#Test Branch created
if(!require("R.matlab")){
  install.packages("R.matlab")
}
if(!require("readxl")){
  install.packages("readxl")
}
if(!require("dplyr")){
  install.packages("dplyr")
}
if(!require("readxl")){
  install.packages("readxl")
}
if(!require("ggplot2")){
  install.packages("ggplot2")
}
if(!require("caret")){
  install.packages("caret")
}
if(!require("glmnet")){
  install.packages("glmnet")
}
if(!require("WeightedROC")){
  install.packages("WeightedROC")
}
if(!require("gbm")){
  install.packages("gbm")
}
if(!require("DMwR")){
  install.packages("DMwR")
}
if(!require("OpenImageR")){
 install.packages("OpenImageR")
}
if(!require("AUC")){
 install.packages("AUC")
}
if(!require("e1071")){
 install.packages("e1071")
}
if(!require("randomForest")){
 install.packages("randomForest")
}
if(!require("xgboost")){
 install.packages("xgboost")
}
if(!require("tibble")){
 install.packages("tibble")
}
if(!require("ROSE")){
 install.packages("ROSE")
}
if(!require("tidyverse")){
 install.packages("tidyverse")
}
if(!require("caTools")){
  install.packages("caTools")
}
if(!require("prediction")){
  install.packages("prediction")
}
if(!require("pROC")){
  install.packages("pROC")
}

library(R.matlab)
library(readxl)
library(dplyr)
library(ggplot2)
library(caret)
library(glmnet)
library(WeightedROC)
library(gbm)
library(DMwR)
### new libraries
library(OpenImageR)
library(AUC)
library(e1071)
library(randomForest)
library(xgboost)
library(tibble)
library(ROSE)
library(tidyverse)
library(caTools)
library(prediction)
library(pROC)

Step 0 set work directories

set.seed(2020)

Provide directories for training images. Training images and Training fiducial points will be in different subfolders.

train_dir <- "../data/train_set/" # This will be modified for different data sets.
train_image_dir <- paste(train_dir, "images/", sep="")
train_pt_dir <- paste(train_dir,  "points/", sep="")
train_label_path <- paste(train_dir, "label.csv", sep="")

Step 1: set up controls for evaluation experiments.

In this chunk, we have a set of controls for the evaluation experiments.

run.presentation.day <- TRUE #presentation day flag. No training. Generate a csv file
run.cv <- TRUE # run cross-validation on the training set
sample.reweight <- TRUE # run sample reweighting in model training
K <- 5  # number of CV folds
run.feature.train <- TRUE # process features for training set
run.test <- TRUE # run evaluation on an independent test set
run.feature.test <- TRUE # process features for test set

# gbm
run.gbm.train <- TRUE # gbm(imroved) is the chosen advanced model
run.gbm.test <- TRUE # gbm(imroved) is the chosen advanced model
gbm.numtrees <- 1000 #number of trees to use in gbm

#features options
run.poly.feature <- TRUE # process poly features
run.add.poly.feature <- TRUE # and poly features to features matrix

# svm
run.svm <- FALSE # svm is the chosen advanced model
model.selection <- TRUE # perform model selection on svm models
run.svm.test <- TRUE # evaluate performance on the test set

# random forest
run.rf <- FALSE # random forest is the chosen advanced model
run.balanced.data <- TRUE # whether or not balance the data
train.random.forest <- FALSE # train random forest model
test.random.forest <- TRUE # test random forest model
tune.random.forest <- FALSE # tune random forest model

# ridge
run.ridge <- FALSE # ridge is the chosen advanced model
alpha <- 0 # ridge regression
train.ridge <- TRUE # train ridge model

# PCA + LDA
run.pca_lda <- FALSE # PCA + LDA is the chosen adcanced model
run.select_PC <- TRUE #run different PCs
run.lda <- TRUE # run lda on the training set
run.pca_lad.test <- TRUE # evaluate performance on the test set

Using cross-validation or independent test set evaluation, we compare the performance of models with different specifications.

Step 2: import data and train-test split

#train-test split
info <- read.csv(train_label_path)
n <- nrow(info) #get number of rows from csv
n_train <- round(n*(4/5), 0) #use 4/5 amount of data for training
train_idx <- sample(info$Index, n_train, replace = F) #grab indexes used for training
test_idx <- setdiff(info$Index, train_idx) # get indexes not used for training

Fiducial points are stored in matlab format. In this step, we read them and store them in a list.

#function to read fiducial points
#input: index
#output: matrix of fiducial points corresponding to the index
n_files <- length(list.files(train_image_dir,'*jpg'))
readMat.matrix <- function(index){
     return(round(readMat(paste0(train_pt_dir, sprintf("%04d", index), ".mat"))[[1]],0))
}

if (run.presentation.day){
  test_idx <- c(1:n_files) #sample(n_files, n_files, replace = F)
  run.gbm.train <- FALSE
  run.feature.train <- FALSE
  run.gbm.test <- TRUE
}

#load fiducial points
fiducial_pt_list <- lapply(1:n_files, readMat.matrix)
save(fiducial_pt_list, file="../output/fiducial_pt_list.RData")

Step 3: construct features and responses

Figure1

feature.R is the wrapper for all feature engineering functions and options. The function feature( ) have options that correspond to different scenarios for the project and produces an R object that contains features and responses that are required by all the models that are going to be evaluated later.

source("../lib/feature.R")
tm_feature_train <- NA
gbm_tm_feature_train <- NA
if(run.feature.train){
  tm_feature_train <- system.time(dat_train<-feature(fiducial_pt_list,train_idx, run.poly.feature, run.add.poly.feature))
  gbm_tm_feature_train <- system.time(gbm_dat_train<-feature(fiducial_pt_list,train_idx, FALSE, FALSE))
  save(gbm_dat_train, file="../output/gbm_feature_train.RData")
  save(dat_train, file="../output/feature_train.RData")
}else{
  load(file="../output/feature_train.RData")
  load(file="../output/gbm_feature_train.RData")
}

tm_feature_test <- NA
gbm_tm_feature_test <- NA
if(run.feature.test){
  tm_feature_test <- system.time(dat_test <- feature(fiducial_pt_list, test_idx, run.poly.feature, run.add.poly.feature))
  gbm_tm_feature_test <- system.time(gbm_dat_test <- feature(fiducial_pt_list, test_idx, FALSE, FALSE))
  save(gbm_dat_test, file="../output/gbm_feature_test.RData")
  save(dat_test, file="../output/feature_test.RData")
}else{
  load(file="../output/feature_test.RData")
  load(file="../output/gbm_feature_test.RData")
}

Step 4: train classification models with training features and responses; run test on test images

Call the train model and test model from library.

train.R and test.R are wrappers for all model training steps and classification/prediction steps.

source("../lib/train.R") 
source("../lib/test.R")

Baseline Model

——–THIS IS TO SEPARATE EACH MODEL. THIS IS TO SEPARATE EACH MODEL. THIS IS TO SEPARATE EACH MODEL.———-

Advanced Model 1: Improved GBM Model

if (run.gbm.train){
  if (sample.reweight){
    
    gbm_dat_train$label <- as.factor(gbm_dat_train$label)
    dat_train_balanced_gbm <- SMOTE(label ~ ., gbm_dat_train, perc.over = 100, perc.under=200)
    #save(dat_train_balanced_rose, file="../output/balanced_data.RData")
    table(dat_train_balanced_gbm$label)
    
    gbm_tm_train <- system.time(gbm_train <- train_gbm(dat_train_balanced_gbm, s=0.1, K=K, n=gbm.numtrees,w = NULL))
    
  } else {
    gbm_tm_train <- system.time(gbm_train <- train_gbm(gbm_dat_train, s=0.1, K=K, n=gbm.numtrees,w = NULL))
  }
  
  # plot the performance
  best.iter.oob <- gbm.perf(gbm_train,method="OOB")  # returns out-of-bag estimated best number of trees
  print(best.iter.oob)
  best.iter.cv <- gbm.perf(gbm_train,method="cv")   # returns K-fold cv estimate of best number of trees
  print(best.iter.cv)
  
  saveRDS(gbm_train, "../output/gbm_model.rds")
  save(gbm_tm_train, best.iter.cv, file="../output/gbm_outputs.RData")
}
if(run.gbm.test){
  load(file="../output/gbm_outputs.RData")
  gbm_tm_test = NA
  feature_test <- as.matrix(gbm_dat_test[, 1:ncol(gbm_dat_test)-1])
  
  gbm_train <- readRDS("../output/gbm_model.rds")
  gbm_tm_test <- system.time(prob_pred_baseline<-test_gbm(gbm_train,as.data.frame(feature_test),n=best.iter.cv,pred.type = 'response'))
  
  label_pred_baseline <- colnames(prob_pred_baseline)[apply(prob_pred_baseline, 1, which.max)]
}

show gbm accuracy and AUC

if (run.gbm.test){
  load(file="../output/gbm_outputs.RData")
  gbm_accu <- mean(gbm_dat_test$label == label_pred_baseline)
  gbm.auc <- WeightedROC(as.numeric(label_pred_baseline), gbm_dat_test$label)
  gbm_auc = WeightedAUC(gbm.auc)
  cat("Time for constructing gbm training features=", gbm_tm_feature_train[1], "s \n")
  cat("Time for constructing gbm testing features=", gbm_tm_feature_test[1], "s \n")
  cat("The AUC of gbm model is", gbm_auc, ".\n")
  cat("The accuracy of GBM baseline model is", gbm_accu*100, "%.\n")
  cat("Time for training gbm model=", gbm_tm_train[1], "s \n") 
  cat("Time for testing model=", gbm_tm_test[1], "s \n")
}

——–THIS IS TO SEPARATE EACH MODEL. THIS IS TO SEPARATE EACH MODEL. THIS IS TO SEPARATE EACH MODEL.———-

Advanced Model 2: Random Forest

if(run.rf){
  # transfer label column from factor to numeric
  dat_train$label <- as.numeric(dat_train$label)
  dat_test$label <- as.numeric(dat_test$label)

  if(run.balanced.data){
    dat_train_balanced_rose<-ROSE(label~., dat_train,seed=2020)$data
    save(dat_train_balanced_rose, file="../output/balanced_train_data_rose.RData")
    dat_test_balanced_rose <- ROSE(label~., dat_test, seed=2020)$data
    save(dat_test_balanced_rose, file = "../output/balanced_test_data_rose.RData")
  } else {
    load(file = "../output/balanced_train_data_rose.RData")
    load(file = "../output/balanced_test_data_rose.RData")
  }
  table(dat_train_balanced_rose$label)
  table(dat_test_balanced_rose$label)
}
if(run.rf){
  source("../lib/random_forest_old_feature.R")
  if(tune.random.forest){
    time.rf.tune <- system.time(rf.tune <- random_forest_tune(dat_train_balanced_rose))
    save(rf.tune, file="../output/rf_tune.RData")
    save(time.rf.tune, file = "../output/rf_tune_time.RData")
  }else{
    load("../output/rf_tune.RData")
    load("../output/rf_tune_time.RData")
  }
  rf.tune
  time.rf.tune[1]
}

mtry = 308 is the best.

if(run.rf){
  source("../lib/random_forest_old_feature.R")
  if(tune.random.forest){
    # Train 500 trees:
    time.rf.train.tree500 <- system.time(random_forest_fit_500_trees <-
                                           random_forest_train_500(dat_train_balanced_rose,mtry = 308))
    save(random_forest_fit_500_trees, file = "../output/rf_train_500_trees.RData")
    save(time.rf.train.tree500, file = "../output/rf_train_500_trees_time.RData")
    cat("500 tree time", time.rf.train.tree500)
    # Train 1000 trees:
    time.rf.train.tree1000 <- system.time(random_forest_fit_1000_trees <-
                                            random_forest_train_1000(dat_train_balanced_rose,mtry = 308))
    save(random_forest_fit_1000_trees, file = "../output/rf_train_1000_trees.RData")
    save(time.rf.train.tree1000, file = "../output/rf_train_1000_trees_time.RData")
    cat("1000 tree time", time.rf.train.tree1000)
    # Train 1500 trees:
    time.rf.train.tree1500 <- system.time(random_forest_fit_1500_trees <-
                                            random_forest_train_1500(dat_train_balanced_rose,mtry = 308))
    save(random_forest_fit_1500_trees, file = "../output/rf_train_1500_trees.RData")
    save(time.rf.train.tree1500, file = "../output/rf_train_1500_trees_time.RData")
    cat("1500 tree time", time.rf.train.tree1500)
    # Train 2000 trees:
    time.rf.train.tree2000 <- system.time(random_forest_fit_2000_trees <-
                                            random_forest_train_2000(dat_train_balanced_rose,mtry = 308))
    save(random_forest_fit_2000_trees, file = "../output/rf_train_2000_trees.RData")
    save(time.rf.train.tree2000, file = "../output/rf_train_2000_trees_time.RData")
    cat("2000 tree time", time.rf.train.tree2000)
    # Train 2500 trees:
    time.rf.train.tree2500 <- system.time(random_forest_fit_2500_trees <-
                                            random_forest_train_2500(dat_train_balanced_rose,mtry = 308))
    save(random_forest_fit_2500_trees, file = "../output/rf_train_2500_trees.RData")
    save(time.rf.train.tree2500, file = "../output/rf_train_2500_trees_time.RData")
    cat("2500 tree time", time.rf.train.tree2500)

    # Train 10 nodes
    time.rf.train.node10 <- system.time(random_forest_fit_10_nodes <-
                                          random_forest_train_10(dat_train_balanced_rose,mtry = 308))
    save(random_forest_fit_10_nodes, file = "../output/rf_train_10_nodes.RData")
    save(time.rf.train.node10, file = "../output/rf_train_10_nodes_time.RData")
    cat("10 node time", time.rf.train.node10)
    # Train 15 nodes
    time.rf.train.node15 <- system.time(random_forest_fit_15_nodes <-
                                          random_forest_train_15(dat_train_balanced_rose,mtry = 308))
    save(random_forest_fit_15_nodes, file = "../output/rf_train_15_nodes.RData")
    save(time.rf.train.node15, file = "../output/rf_train_15_nodes_time.RData")
    cat("15 node time", time.rf.train.node15)
    # Train 20 nodes
    time.rf.train.node20 <- system.time(random_forest_fit_20_nodes <-
                                          random_forest_train_20(dat_train_balanced_rose,mtry = 308))
    save(random_forest_fit_20_nodes, file = "../output/rf_train_20_nodes.RData")
    save(time.rf.train.node20, file = "../output/rf_train_20_nodes_time.RData")
    cat("20 node time", time.rf.train.node20)
    # Train 25 nodes
    time.rf.train.node25 <- system.time(random_forest_fit_25_nodes <-
                                          random_forest_train_25(dat_train_balanced_rose,mtry = 308))
    save(random_forest_fit_25_nodes, file = "../output/rf_train_25_nodes.RData")
    save(time.rf.train.node25, file = "../output/rf_train_25_nodes_time.RData")
    cat("25 node time", time.rf.train.node25)
    # Train 30 nodes
    time.rf.train.node30 <- system.time(random_forest_fit_30_nodes <-
                                          random_forest_train_30(dat_train_balanced_rose,mtry = 308))
    save(random_forest_fit_30_nodes, file = "../output/rf_train_30_nodes.RData")
    save(time.rf.train.node30, file = "../output/rf_train_30_nodes_time.RData")
    cat("30 node time", time.rf.train.node30)
  } else {
    load("../output/rf_train_500_trees.RData")
    load("../output/rf_train_1000_trees.RData")
    load("../output/rf_train_1500_trees.RData")
    load("../output/rf_train_2000_trees.RData")
    load("../output/rf_train_2500_trees.RData")
    load("../output/rf_train_10_nodes.RData")
    load("../output/rf_train_15_nodes.RData")
    load("../output/rf_train_20_nodes.RData")
    load("../output/rf_train_25_nodes.RData")
    load("../output/rf_train_30_nodes.RData")
  }



  #Error rate of each hyperparameter:



  # Evaluate each hyperparameter


  # Predicted value from 500 trees' model:
  rf_predicted_balanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_500_trees,
                                                                   dat_test_balanced_rose)))
  rf_predicted_imbalanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_500_trees, dat_test)))
  # Evaluate 500 trees:
  rf_accuracy_balanced <- mean(round(rf_predicted_balanced == dat_test_balanced_rose$label))
  tpr.fpr.balanced <- WeightedROC(as.numeric(rf_predicted_balanced),dat_test_balanced_rose$label)
  rf_AUC_balanced <- WeightedAUC(tpr.fpr.balanced)
  rf_accuracy_imbalanced <- mean(round(rf_predicted_imbalanced == dat_test$label))
  tpr.fpr.imbalanced <- WeightedROC(as.numeric(rf_predicted_imbalanced),dat_test$label)
  rf_AUC_imbalanced <- WeightedAUC(tpr.fpr.imbalanced)

  cat("Accuracy(balanced) 500", rf_accuracy_balanced*100,"%.\n")
  cat("AUC(balanced) 500", rf_AUC_balanced,".\n")
  cat("Accuracy(imbalanced) 500", rf_accuracy_imbalanced*100,"%.\n")
  cat("AUC(imbalanced) 500",rf_AUC_imbalanced,".\n")


  # Evaluation from 1000 trees' model:
  rf_predicted_balanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_1000_trees,
                                                                   dat_test_balanced_rose)))
  rf_predicted_imbalanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_1000_trees, dat_test)))

  rf_accuracy_balanced <- mean(round(rf_predicted_balanced == dat_test_balanced_rose$label))
  tpr.fpr.balanced <- WeightedROC(as.numeric(rf_predicted_balanced),dat_test$label)
  rf_AUC_balanced <- WeightedAUC(tpr.fpr.balanced)
  rf_accuracy_imbalanced <- mean(round(rf_predicted_imbalanced == dat_test_balanced_rose$label))
  tpr.fpr.imbalanced <- WeightedROC(as.numeric(rf_predicted_imbalanced),dat_test$label)
  rf_AUC_imbalanced <- WeightedAUC(tpr.fpr.imbalanced)

  cat("Accuracy(balanced) 1000", rf_accuracy_balanced*100,"%.\n")
  cat("AUC(balanced) 1000", rf_AUC_balanced,".\n")
  cat("Accuracy(imbalanced) 1000", rf_accuracy_imbalanced*100,"%.\n")
  cat("AUC(imbalanced) 1000",rf_AUC_imbalanced,".\n")

  # 1500 trees
  rf_predicted_balanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_1500_trees,
                                                                   dat_test_balanced_rose)))
  rf_predicted_imbalanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_1500_trees, dat_test)))

  rf_accuracy_balanced <- mean(round(rf_predicted_balanced == dat_test_balanced_rose$label))
  tpr.fpr.balanced <- WeightedROC(as.numeric(rf_predicted_balanced),dat_test$label)
  rf_AUC_balanced <- WeightedAUC(tpr.fpr.balanced)
  rf_accuracy_imbalanced <- mean(round(rf_predicted_imbalanced == dat_test_balanced_rose$label))
  tpr.fpr.imbalanced <- WeightedROC(as.numeric(rf_predicted_imbalanced),dat_test$label)
  rf_AUC_imbalanced <- WeightedAUC(tpr.fpr.imbalanced)

  cat("Accuracy(balanced) 1500", rf_accuracy_balanced*100,"%.\n")
  cat("AUC(balanced) 1500", rf_AUC_balanced,".\n")
  cat("Accuracy(imbalanced) 1500", rf_accuracy_imbalanced*100,"%.\n")
  cat("AUC(imbalanced) 1500",rf_AUC_imbalanced,".\n")

  #2000 trees
  rf_predicted_balanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_2000_trees,
                                                                   dat_test_balanced_rose)))
  rf_predicted_imbalanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_2000_trees, dat_test)))

  rf_accuracy_balanced <- mean(round(rf_predicted_balanced == dat_test_balanced_rose$label))
  tpr.fpr.balanced <- WeightedROC(as.numeric(rf_predicted_balanced),dat_test$label)
  rf_AUC_balanced <- WeightedAUC(tpr.fpr.balanced)
  rf_accuracy_imbalanced <- mean(round(rf_predicted_imbalanced == dat_test_balanced_rose$label))
  tpr.fpr.imbalanced <- WeightedROC(as.numeric(rf_predicted_imbalanced),dat_test$label)
  rf_AUC_imbalanced <- WeightedAUC(tpr.fpr.imbalanced)

  cat("Accuracy(balanced) 2000", rf_accuracy_balanced*100,"%.\n")
  cat("AUC(balanced) 2000", rf_AUC_balanced,".\n")
  cat("Accuracy(imbalanced) 2000", rf_accuracy_imbalanced*100,"%.\n")
  cat("AUC(imbalanced) 2000",rf_AUC_imbalanced,".\n")

  # 2500 trees
  rf_predicted_balanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_2500_trees,
                                                                   dat_test_balanced_rose)))
  rf_predicted_imbalanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_2500_trees, dat_test)))

  rf_accuracy_balanced <- mean(round(rf_predicted_balanced == dat_test_balanced_rose$label))
  tpr.fpr.balanced <- WeightedROC(as.numeric(rf_predicted_balanced),dat_test$label)
  rf_AUC_balanced <- WeightedAUC(tpr.fpr.balanced)
  rf_accuracy_imbalanced <- mean(round(rf_predicted_imbalanced == dat_test_balanced_rose$label))
  tpr.fpr.imbalanced <- WeightedROC(as.numeric(rf_predicted_imbalanced),dat_test$label)
  rf_AUC_imbalanced <- WeightedAUC(tpr.fpr.imbalanced)

  cat("Accuracy(balanced) 2500", rf_accuracy_balanced*100,"%.\n")
  cat("AUC(balanced) 2500", rf_AUC_balanced,".\n")
  cat("Accuracy(imbalanced) 2500", rf_accuracy_imbalanced*100,"%.\n")
  cat("AUC(imbalanced) 2500",rf_AUC_imbalanced,".\n")

  # 10 nodes
  rf_predicted_balanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_10_nodes,
                                                                   dat_test_balanced_rose)))
  rf_predicted_imbalanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_10_nodes, dat_test)))

  rf_accuracy_balanced <- mean(round(rf_predicted_balanced == dat_test_balanced_rose$label))
  tpr.fpr.balanced <- WeightedROC(as.numeric(rf_predicted_balanced),dat_test$label)
  rf_AUC_balanced <- WeightedAUC(tpr.fpr.balanced)
  rf_accuracy_imbalanced <- mean(round(rf_predicted_imbalanced == dat_test_balanced_rose$label))
  tpr.fpr.imbalanced <- WeightedROC(as.numeric(rf_predicted_imbalanced),dat_test$label)
  rf_AUC_imbalanced <- WeightedAUC(tpr.fpr.imbalanced)

  cat("Accuracy(balanced) 10", rf_accuracy_balanced*100,"%.\n")
  cat("AUC(balanced) 10", rf_AUC_balanced,".\n")
  cat("Accuracy(imbalanced) 10", rf_accuracy_imbalanced*100,"%.\n")
  cat("AUC(imbalanced) 10",rf_AUC_imbalanced,".\n")

  # 15 nodes
  rf_predicted_balanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_15_nodes,
                                                                   dat_test_balanced_rose)))
  rf_predicted_imbalanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_15_nodes, dat_test)))

  rf_accuracy_balanced <- mean(round(rf_predicted_balanced == dat_test_balanced_rose$label))
  tpr.fpr.balanced <- WeightedROC(as.numeric(rf_predicted_balanced),dat_test$label)
  rf_AUC_balanced <- WeightedAUC(tpr.fpr.balanced)
  rf_accuracy_imbalanced <- mean(round(rf_predicted_imbalanced == dat_test_balanced_rose$label))
  tpr.fpr.imbalanced <- WeightedROC(as.numeric(rf_predicted_imbalanced),dat_test$label)
  rf_AUC_imbalanced <- WeightedAUC(tpr.fpr.imbalanced)

  cat("Accuracy(balanced) 15", rf_accuracy_balanced*100,"%.\n")
  cat("AUC(balanced) 15", rf_AUC_balanced,".\n")
  cat("Accuracy(imbalanced) 15", rf_accuracy_imbalanced*100,"%.\n")
  cat("AUC(imbalanced) 15",rf_AUC_imbalanced,".\n")

  # 20 nodes
  rf_predicted_balanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_20_nodes,
                                                                   dat_test_balanced_rose)))
  rf_predicted_imbalanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_20_nodes, dat_test)))

  rf_accuracy_balanced <- mean(round(rf_predicted_balanced == dat_test_balanced_rose$label))
  tpr.fpr.balanced <- WeightedROC(as.numeric(rf_predicted_balanced),dat_test$label)
  rf_AUC_balanced <- WeightedAUC(tpr.fpr.balanced)
  rf_accuracy_imbalanced <- mean(round(rf_predicted_imbalanced == dat_test_balanced_rose$label))
  tpr.fpr.imbalanced <- WeightedROC(as.numeric(rf_predicted_imbalanced),dat_test$label)
  rf_AUC_imbalanced <- WeightedAUC(tpr.fpr.imbalanced)

  cat("Accuracy(balanced) 20", rf_accuracy_balanced*100,"%.\n")
  cat("AUC(balanced) 20", rf_AUC_balanced,".\n")
  cat("Accuracy(imbalanced) 20", rf_accuracy_imbalanced*100,"%.\n")
  cat("AUC(imbalanced) 20",rf_AUC_imbalanced,".\n")

  # 25 nodes
  rf_predicted_balanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_25_nodes,
                                                                   dat_test_balanced_rose)))
  rf_predicted_imbalanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_25_nodes, dat_test)))

  rf_accuracy_balanced <- mean(round(rf_predicted_balanced == dat_test_balanced_rose$label))
  tpr.fpr.balanced <- WeightedROC(as.numeric(rf_predicted_balanced),dat_test$label)
  rf_AUC_balanced <- WeightedAUC(tpr.fpr.balanced)
  rf_accuracy_imbalanced <- mean(round(rf_predicted_imbalanced == dat_test_balanced_rose$label))
  tpr.fpr.imbalanced <- WeightedROC(as.numeric(rf_predicted_imbalanced),dat_test$label)
  rf_AUC_imbalanced <- WeightedAUC(tpr.fpr.imbalanced)

  cat("Accuracy(balanced) 25", rf_accuracy_balanced*100,"%.\n")
  cat("AUC(balanced) 25", rf_AUC_balanced,".\n")
  cat("Accuracy(imbalanced) 25", rf_accuracy_imbalanced*100,"%.\n")
  cat("AUC(imbalanced) 25",rf_AUC_imbalanced,".\n")

  # 30 nodes
  rf_predicted_balanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_30_nodes,
                                                                   dat_test_balanced_rose)))
  rf_predicted_imbalanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_30_nodes, dat_test)))

  rf_accuracy_balanced <- mean(round(rf_predicted_balanced == dat_test_balanced_rose$label))
  tpr.fpr.balanced <- WeightedROC(as.numeric(rf_predicted_balanced),dat_test$label)
  rf_AUC_balanced <- WeightedAUC(tpr.fpr.balanced)
  rf_accuracy_imbalanced <- mean(round(rf_predicted_imbalanced == dat_test_balanced_rose$label))
  tpr.fpr.imbalanced <- WeightedROC(as.numeric(rf_predicted_imbalanced),dat_test$label)
  rf_AUC_imbalanced <- WeightedAUC(tpr.fpr.imbalanced)

  cat("Accuracy(balanced) 30", rf_accuracy_balanced*100,"%.\n")
  cat("AUC(balanced) 30", rf_AUC_balanced,".\n")
  cat("Accuracy(imbalanced) 30", rf_accuracy_imbalanced*100,"%.\n")
  cat("AUC(imbalanced) 30",rf_AUC_imbalanced,".\n")
}

15 Nodes and 2500 trees is the best.

if(run.rf){
  if(train.random.forest){
    time.rf.train.final.balanced <- system.time(random_forest_fit_final_balanced <-
                                                  random_forest_train(dat_train_balanced_rose, 
                                                                      mtry = 308,
                                                                      tree_number = 2000, 
                                                                      node_size = 15))
    save(random_forest_fit_final_balanced, file = "../output/rf_train_final_balanced_old_feature.RData")
    save(time.rf.train.final.balanced, file = "../output/rf_train_final_time_balanced_old_feature.RData")
    time.rf.train.final.imbalanced <- system.time(random_forest_fit_final_imbalanced <-
                                                  random_forest_train(dat_train, 
                                                                      mtry = 308, 
                                                                      tree_number = 1000,
                                                                      node_size = 15))
    save(time.rf.train.final.imbalanced, file = "../output/rf_train_final_time_imbalanced_old_feature.RData")
    save(random_forest_fit_final_imbalanced, file = "../output/rf_train_final_imbalanced_old_feature.RData")
  } else {
    load("../output/rf_train_final_balanced_old_feature.RData")
    load("../output/rf_train_final_time_balanced_old_feature.RData")
    load("../output/rf_train_final_time_imbalanced_old_feature.RData")
    load("../output/rf_train_final_imbalanced_old_feature.RData")
  }
}
if(run.rf){
  # Balanced:
  if(test.random.forest){
  time.rf.test.final.balanced <- system.time(
    rf_predicted_balanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_final_balanced,
                                                                     dat_test_balanced_rose))))
  rf_accuracy_balanced <- mean(round(rf_predicted_balanced == dat_test_balanced_rose$label))
  tpr.fpr.balanced <- WeightedROC(as.numeric(rf_predicted_balanced),dat_test_balanced_rose$label)
  rf_AUC_balanced <- WeightedAUC(tpr.fpr.balanced)

  cat("AUC(balanced): ", rf_AUC_balanced,".\n")
  cat("Accuracy(balanced)", rf_accuracy_balanced*100,"%.\n")
  cat("Training time: ", time.rf.train.final.balanced[1], "s.\n")
  cat("Testing time: ", time.rf.test.final.balanced[1], "s.\n")

  # Imbalanced:
  time.rf.test.final.imbalanced <- system.time(
    rf_predicted_imbalanced <-as.numeric(as.vector(random_forest_test(random_forest_fit_final_imbalanced,
                                                                      dat_test))))
  rf_accuracy_imbalanced <- mean(round(rf_predicted_imbalanced == dat_test$label))
  tpr.fpr.imbalanced <- WeightedROC(as.numeric(rf_predicted_imbalanced),dat_test$label)
  rf_AUC_imbalanced <- WeightedAUC(tpr.fpr.imbalanced)

  cat("AUC(imbalanced): ", rf_AUC_imbalanced,".\n")
  cat("Accuracy(imbalanced)", rf_accuracy_imbalanced*100,"%.\n")
  cat("Training time: ", time.rf.train.final.imbalanced[1], "s.\n")
  cat("Testing time: ", time.rf.test.final.imbalanced[1], "s.\n")
  }
}

——–THIS IS TO SEPARATE EACH MODEL. THIS IS TO SEPARATE EACH MODEL. THIS IS TO SEPARATE EACH MODEL.———-

Advanced Model 3: SVM Model

if(run.svm){
  tm_svm_rebalanced_train <- NA
  if(sample.reweight){
    tm_svm_rebalanced_train <- system.time(svm_training_data <- ROSE(label ~ ., data = dat_train)$data)
    save(svm_training_data, file="../output/svm_training_data.RData")
    save(tm_svm_rebalanced_train, file="../output/tm_svm_rebalanced_train.RData")
  } else {
    svm_training_data <- dat_train
    tm_svm_rebalanced_train <- tm_feature_train
  }
} #else {
  #load(file="../output/tm_svm_rebalanced_train.RData")
#}
if(run.svm){
  tm_svm_linear_mod <- NA
  tm_svm_radial_mod <- NA
  
  if(model.selection){
    svm_model_auc <- rep(NA, 2)
    
    ### linear kernel
    if(run.cv){
      #best.linear.cost <- svm_linear_cost_tune(svm_training_data)
      #cat("The best cost for svm model with linear kernel is: ", best.linear.cost$best.parameters$cost)
      tm_svm_linear_mod <- system.time(svm_linear_mod <- svm_linear_train(svm_training_data, 0.01, K))
      save(svm_linear_mod, file="../output/svm_linear_mod.RData")
      save(tm_svm_linear_mod, file="../output/tm_svm_linear_mod.RData")
    } else {
      load(file="../output/svm_linear_mod.RData")
      load(file="../output/tm_svm_linear_mod.RData")
    }
    svm_linear_pred <- svm_test(svm_linear_mod, svm_training_data, TRUE)
    #mean(round(svm_linear_pred == svm_training_data$label))
    svm_linear_accu <- mean(round(svm_linear_pred == svm_training_data$label))
    tpr.fpr_linear <- WeightedROC(as.numeric(svm_linear_pred), svm_training_data$label)
    svm_model_auc[1] <- WeightedAUC(tpr.fpr_linear)
    
    
    ### radial basis kernel
    if(run.cv){
      #best.radial.cost <- svm_radial_cost_tune(svm_training_data)
      #radial_cost = best.radial.cost$best.parameters$cost
      #radial_gamma = best.radial.cost$best.parameters$gamma
      
      tm_svm_radial_mod < system.time(svm_radial_mod <- svm_radial_train(svm_training_data, 1, K))
      save(svm_radial_mod, file="../output/svm_radial_mod.RData")
      save(tm_svm_radial_mod, file="../output/tm_svm_radial_mod.RData")
    } else { 
      load(file="../output/svm_radial_mod.RData")
      load(file="../output/tm_svm_radial_mod.RData")
    }
    svm_radial_pred <- svm_test(svm_radial_mod, svm_training_data, TRUE)
    # evaluate performance
    svm_radial_accu <- mean(round(svm_radial_pred == svm_training_data$label))
    tpr.fpr_default <- WeightedROC(as.numeric(svm_radial_pred), svm_training_data$label)
    svm_model_auc[2] <- WeightedAUC(tpr.fpr_default)
  } else {
    load(file="../output/svm_linear_mod.RData")
    load(file="../output/tm_svm_linear_mod.RData")
    load(file="../output/svm_radial_mod.RData")
    load(file="../output/tm_svm_radial_mod.RData")
  }
  
  
  ### Evaluation on Testing Data
  tm_svm_rebalanced_test <- NA
  if(sample.reweight){
    tm_svm_rebalanced_test <- system.time(svm_testing_data <- ROSE(label ~ ., data = dat_test)$data)
    save(svm_testing_data, file="../output/svm_testing_data.RData")
    save(tm_svm_rebalanced_test, file="../output/tm_svm_rebalanced_test.RData")
  } else {
    svm_testing_data <- dat_test
    tm_svm_rebalanced_test <- tm_feature_test
  }
  
  if(run.svm.test){
    svm_auc <- rep(NA, 2)
    svm_accu <- rep(NA, 2)
    ## linear
    tm_svm_linear_test <- system.time(svm_linear_pred <- svm_test(svm_linear_mod, svm_testing_data))
    svm_accu[1] = mean(round(svm_linear_pred == svm_testing_data$label))
    tpr.fpr.linear <- WeightedROC(as.numeric(svm_linear_pred), svm_testing_data$label)
    svm_auc[1] = WeightedAUC(tpr.fpr.linear)
    ## rbf
    tm_svm_rbf_test <- system.time(svm_rbf_pred <- svm_test(svm_radial_mod, svm_testing_data))
    svm_accu[2] = mean(round(svm_rbf_pred == svm_testing_data$label))
    tpr.fpr.rbf <- WeightedROC(as.numeric(svm_linear_pred), svm_testing_data$label)
    svm_auc[2] = WeightedAUC(tpr.fpr.rbf)
    
    save(tm_svm_radial_mod, file="../output/tm_svm_linear_test.RData")
    
    ## performance
    svm_auc
    svm_accu

    cat("The accuracy of svm model is", svm_accu[2]*100, "%.\n")
    cat("The AUC of svm model is", svm_auc[2], ".\n")
  } else {
    load(file="../output/tm_svm_rebalanced_test.RData")
  }
}# else {
  #load(file="../output/svm_radial_mod.RData")
  #load(file="../output/tm_svm_radial_mod.RData")
#}
if(run.svm){
  #cat("Time for rebalancing training data =", tm_svm_rebalanced_train[1], "s \n")
  #cat("Time for rebalancing testing data =", tm_svm_rebalanced_test[1], "s \n")
  #cat("Time for training svm model =", tm_svm_radial_mod[1], "s \n")
  cat("Time for testing svm model=", tm_svm_rbf_test[1], "s \n")
}

——–THIS IS TO SEPARATE EACH MODEL. THIS IS TO SEPARATE EACH MODEL. THIS IS TO SEPARATE EACH MODEL.———-

Advanced Model 4: Ridge Model

if(run.ridge){
  tm_ridge_train <- NA
  if (train.ridge){
    dat_train_rebalanced <- ROSE(label ~ ., data = dat_train, seed=2020)$data
    tm_ridge_train <- system.time(ridge_cv_model<-ridge_train(train_data=dat_train_rebalanced, alpha=alpha, K=K, lambda=lambda))
    save(ridge_cv_model, file="../output/ridge_cv_model.RData")
    save(tm_ridge_train, file="../output/ridge_train_time.RData")
  } else {
    load(file="../output/ridge_cv_model.RData")
    load(file="../output/ridge_train_time.RData")
  }
}
if(run.ridge){
  if (run.cv){
    set.seed(2020)
    dat_train_rebalanced <- ROSE(label ~ ., data = dat_train, seed=2020)$data
    feature_train = as.matrix(dat_train_rebalanced[,-dim(dat_train_rebalanced)[2]])
    label_train = as.integer(dat_train_rebalanced$label)
    ridge_model = cv.glmnet(x=feature_train, y=label_train, alpha=alpha, nfolds=K, lambda=lambda)
    opt_lambda = ridge_model$lambda.min
    save(opt_lambda, file="../output/ridge_optimal_lambda.RData")
  } else {
    load(file="../output/ridge_optimal_lambda.RData")
  }
}
if(run.ridge){
  tm_ridge_test = NA
  if(run.test){
    load("../output/ridge_cv_model.RData")
    dat_test_rebalanced <- ROSE(label ~ ., data = dat_test, seed=2020)$data
    feature_test <- as.matrix(dat_test_rebalanced[, -dim(dat_test_rebalanced)[2]])
    tm_ridge_test <- system.time(label_pred<-as.integer(ridge_test(model=ridge_cv_model, features=feature_test, pred.type = 'class')))
    save(tm_ridge_test, file="../output/ridge_test_time.RData")
  } else{
    load(file="../output/ridge_test_time.RData")
  }
}
if(run.ridge){
  cat("Time for constructing training features=", tm_feature_train[1], "s \n")
  cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
  cat("Time for training ridge model=", tm_ridge_train[1], "s \n") 
  cat("Time for testing ridge model=", tm_ridge_test[1], "s \n")
}
if(run.ridge){
  load("../output/ridge_cv_model.RData")
  dat_test_rebalanced <- ROSE(label ~ ., data = dat_test, seed=2020)$data
  feature_test <- as.matrix(dat_test_rebalanced[, -dim(dat_test_rebalanced)[2]])
  label_pred = as.integer(predict(ridge_cv_model, s=opt_lambda, newx=feature_test, type='class'))
  label_test = as.integer(dat_test_rebalanced$label)
  ridge_accuracy = mean(round(label_test== label_pred))
  cat("The accuracy of the ridge model is", ridge_accuracy*100, "%.\n")
  ridge_AUC = auc(roc(label_pred,label_test))
  cat("The AUC of the ridge model is", ridge_AUC, ".\n")
}

——–THIS IS TO SEPARATE EACH MODEL. THIS IS TO SEPARATE EACH MODEL. THIS IS TO SEPARATE EACH MODEL.———-

Advanced Model 5: PCA + LDA

if(run.pca_lda){
  if(sample.reweight){
    balanced_train_data <- ROSE(label~.,data = dat_train)$data
    save(balanced_train_data, file="../output/feature_balanced_train.RData")
  } else {
    load(balanced_train_data, file="../output/feature_train.RData")
  }
}

Since there are over 6000 features, we implement the PCA method to reduce dimension according to the covariance matrix. We only retain PCs with large variance.

if(run.pca_lda){
  if(run.pca_lad.test){
    if(sample.reweight){
      balanced_test_data <- ROSE(label~.,data = dat_test)$data
      save(balanced_test_data, file="../output/feature_balanced_test.RData")
    } else {
      load(balanced_test_data, file="../output/feature_balanced_test.RData")
    }
  }
  
  if(run.select_PC){
    #separate the features from label
    dat_train_new <- balanced_train_data[,-dim(balanced_train_data)[2]]
    dat_test_new <- balanced_test_data[,-dim(balanced_test_data)[2]]
    #create a vector contain target number of PCs
    num.pca <- c(10,50,500,1000)
    train_pca <- function(num.pca){
      for(i in 1:length(num.pca)){
        #start time for training the model
        train.model.start = proc.time()
        #run PCA
        pca <- prcomp(dat_train_new)
        #store for each potential PC
        train_pca <- data.frame(pca$x[,1:num.pca[i]], label = balanced_train_data[dim(balanced_train_data)[2]])
        pred_pca <- predict(pca,dat_test_new)
        test_pca <- data.frame(pred_pca[,1:num.pca[i]], label = balanced_test_data[dim(balanced_test_data)[2]])
        #fitting the lda model
        lda_pca <- lda(label ~ ., data = train_pca) 
        #stop time for training the model
        train.model.end = proc.time()
        #start time for testing the model
        test.model.start = proc.time()
        #predict lda model
        lda_pred_pca = predict(lda_pca,test_pca[-dim(test_pca)[2]])
        #end time for testing the model
        test.model.end = proc.time()
        #test accuracy
        test_accuracy=confusionMatrix(lda_pred_pca$class, test_pca$label)$overall[1]
        print(list(l1=train.model.end - train.model.start,
               l2=test.model.end - test.model.start,
               l3=test_accuracy))
      }
    }
  train_pca(num.pca)
  }
}

By comparing the training time, test time and accuracy, we use model with 50 PCs.

if(run.pca_lda){
  train.model.start = proc.time()
  if(run.lda){
    pca_10 <- prcomp(dat_train_new)
    train_pca_10 <- data.frame(pca_10$x[,1:50], label = balanced_train_data[dim(balanced_train_data)[2]])
    pred_pca_10 <- predict(pca_10,dat_test_new)
    test_pca_10 <- data.frame(pred_pca_10[,1:50], label = balanced_test_data[dim(balanced_test_data)[2]])
    save(train_pca_10, file="../output/feature_pca_train.RData")
    save(test_pca_10, file="../output/feature_pca_test.RData")  
  } else {
    load(train_pca_10, file="../output/feature_pca_train.RData")
    load(test_pca_10, file="../output/feature_pca_test.RData")  
  }
  #calculate the training time
  lda_pca_10 <- lda(label ~ ., data = train_pca_10, cv = TRUE)
  train.model.end = proc.time()
}
if(run.pca_lda){
  test.model.start = proc.time()
  pred_train_lda <- predict(lda_pca_10, train_pca_10[-dim(train_pca_10)[2]])
  accu_train_lda <- mean(pred_train_lda$class == train_pca_10$label)
  cat("The trainig accuracy of model: LDA", "is", accu_train_lda*100, "%.\n")
  #calculating the test time
  if(run.test){
    pred_test_lda <- predict(lda_pca_10, test_pca_10)
  }
  test.model.end = proc.time()
  save(pred_test_lda, file="../output/fit_train.RData")
  accu_test_lda <- mean(pred_test_lda$class == test_pca_10$label)
  cat("The accuracy of model: LDA", "is", accu_test_lda*100, "%.\n")
  tpr.fpr <- WeightedROC(as.numeric(pred_test_lda$class), test_pca_10$label)
  lda_auc = WeightedAUC(tpr.fpr)
  cat("The AUC of model: LDA is", lda_auc, ".\n")
}

Prediction performance matters, so does the running times for constructing features and for training the model, especially when the computation resource is limited.

if(run.pca_lda){
  tm_train <- train.model.end - train.model.start
  tm_test <- test.model.end - test.model.start
  cat("Time for constructing training features=", tm_feature_train[1], "s \n")
  cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
  cat("Time for training model=", tm_train[1], "s \n") 
  cat("Time for testing model=", tm_test[1], "s \n")
}
if (run.presentation.day){
  csvfileoutput<-"../output/label_prediction.csv"
  #Advanced<-
  Baseline<-label_pred_baseline
  Index<-test_idx
  csvdata <- data.frame(Index, Baseline)
  
  write.csv(csvdata,csvfileoutput, row.names=FALSE,quote = FALSE)
  
  
  #presentation_day_gbm_accu <- mean(info$label == label_pred_baseline)
  #cat("The accuracy of GBM baseline model is", presentation_day_gbm_accu*100, "%.\n")
  
}

Reference

  • Du, S., Tao, Y., & Martinez, A. M. (2014). Compound facial expressions of emotion. Proceedings of the National Academy of Sciences, 111(15), E1454-E1462.
LS0tCnRpdGxlOiAiUHJvamVjdCAzOiBGYWNpYWwgRXhwcmVzc2lvbiBQcmVkaWN0aXZlIE1vZGVsaW5nIgphdXRob3I6ICJKaW5nYmluIENhbywgQ2h1YW5jaHVhbiBMaXUsIERlbm5pcyBTaHBpdHMsIFlpbmd5YW8gV3UsIFppa3VuIFpodWFuZyIKb3V0cHV0OgogIHBkZl9kb2N1bWVudDogZGVmYXVsdAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBodG1sX2RvY3VtZW50OgogICAgZGZfcHJpbnQ6IHBhZ2VkCi0tLQoKYGBge3IgbWVzc2FnZT1GQUxTRX0KI1Rlc3QgQnJhbmNoIGNyZWF0ZWQKaWYoIXJlcXVpcmUoIlIubWF0bGFiIikpewogIGluc3RhbGwucGFja2FnZXMoIlIubWF0bGFiIikKfQppZighcmVxdWlyZSgicmVhZHhsIikpewogIGluc3RhbGwucGFja2FnZXMoInJlYWR4bCIpCn0KaWYoIXJlcXVpcmUoImRwbHlyIikpewogIGluc3RhbGwucGFja2FnZXMoImRwbHlyIikKfQppZighcmVxdWlyZSgicmVhZHhsIikpewogIGluc3RhbGwucGFja2FnZXMoInJlYWR4bCIpCn0KaWYoIXJlcXVpcmUoImdncGxvdDIiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpCn0KaWYoIXJlcXVpcmUoImNhcmV0IikpewogIGluc3RhbGwucGFja2FnZXMoImNhcmV0IikKfQppZighcmVxdWlyZSgiZ2xtbmV0IikpewogIGluc3RhbGwucGFja2FnZXMoImdsbW5ldCIpCn0KaWYoIXJlcXVpcmUoIldlaWdodGVkUk9DIikpewogIGluc3RhbGwucGFja2FnZXMoIldlaWdodGVkUk9DIikKfQppZighcmVxdWlyZSgiZ2JtIikpewogIGluc3RhbGwucGFja2FnZXMoImdibSIpCn0KaWYoIXJlcXVpcmUoIkRNd1IiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiRE13UiIpCn0KaWYoIXJlcXVpcmUoIk9wZW5JbWFnZVIiKSl7CiBpbnN0YWxsLnBhY2thZ2VzKCJPcGVuSW1hZ2VSIikKfQppZighcmVxdWlyZSgiQVVDIikpewogaW5zdGFsbC5wYWNrYWdlcygiQVVDIikKfQppZighcmVxdWlyZSgiZTEwNzEiKSl7CiBpbnN0YWxsLnBhY2thZ2VzKCJlMTA3MSIpCn0KaWYoIXJlcXVpcmUoInJhbmRvbUZvcmVzdCIpKXsKIGluc3RhbGwucGFja2FnZXMoInJhbmRvbUZvcmVzdCIpCn0KaWYoIXJlcXVpcmUoInhnYm9vc3QiKSl7CiBpbnN0YWxsLnBhY2thZ2VzKCJ4Z2Jvb3N0IikKfQppZighcmVxdWlyZSgidGliYmxlIikpewogaW5zdGFsbC5wYWNrYWdlcygidGliYmxlIikKfQppZighcmVxdWlyZSgiUk9TRSIpKXsKIGluc3RhbGwucGFja2FnZXMoIlJPU0UiKQp9CmlmKCFyZXF1aXJlKCJ0aWR5dmVyc2UiKSl7CiBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQp9CmlmKCFyZXF1aXJlKCJjYVRvb2xzIikpewogIGluc3RhbGwucGFja2FnZXMoImNhVG9vbHMiKQp9CmlmKCFyZXF1aXJlKCJwcmVkaWN0aW9uIikpewogIGluc3RhbGwucGFja2FnZXMoInByZWRpY3Rpb24iKQp9CmlmKCFyZXF1aXJlKCJwUk9DIikpewogIGluc3RhbGwucGFja2FnZXMoInBST0MiKQp9CgpsaWJyYXJ5KFIubWF0bGFiKQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KGdsbW5ldCkKbGlicmFyeShXZWlnaHRlZFJPQykKbGlicmFyeShnYm0pCmxpYnJhcnkoRE13UikKIyMjIG5ldyBsaWJyYXJpZXMKbGlicmFyeShPcGVuSW1hZ2VSKQpsaWJyYXJ5KEFVQykKbGlicmFyeShlMTA3MSkKbGlicmFyeShyYW5kb21Gb3Jlc3QpCmxpYnJhcnkoeGdib29zdCkKbGlicmFyeSh0aWJibGUpCmxpYnJhcnkoUk9TRSkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoY2FUb29scykKbGlicmFyeShwcmVkaWN0aW9uKQpsaWJyYXJ5KHBST0MpCmBgYAoKCiMjIyBTdGVwIDAgc2V0IHdvcmsgZGlyZWN0b3JpZXMKYGBge3Igd2tkaXIsIGV2YWw9RkFMU0V9CnNldC5zZWVkKDIwMjApCmBgYAoKUHJvdmlkZSBkaXJlY3RvcmllcyBmb3IgdHJhaW5pbmcgaW1hZ2VzLiBUcmFpbmluZyBpbWFnZXMgYW5kIFRyYWluaW5nIGZpZHVjaWFsIHBvaW50cyB3aWxsIGJlIGluIGRpZmZlcmVudCBzdWJmb2xkZXJzLiAKCmBgYHtyfQp0cmFpbl9kaXIgPC0gIi4uL2RhdGEvdHJhaW5fc2V0LyIgIyBUaGlzIHdpbGwgYmUgbW9kaWZpZWQgZm9yIGRpZmZlcmVudCBkYXRhIHNldHMuCnRyYWluX2ltYWdlX2RpciA8LSBwYXN0ZSh0cmFpbl9kaXIsICJpbWFnZXMvIiwgc2VwPSIiKQp0cmFpbl9wdF9kaXIgPC0gcGFzdGUodHJhaW5fZGlyLCAgInBvaW50cy8iLCBzZXA9IiIpCnRyYWluX2xhYmVsX3BhdGggPC0gcGFzdGUodHJhaW5fZGlyLCAibGFiZWwuY3N2Iiwgc2VwPSIiKQpgYGAKCiMjIyBTdGVwIDE6IHNldCB1cCBjb250cm9scyBmb3IgZXZhbHVhdGlvbiBleHBlcmltZW50cy4KCkluIHRoaXMgY2h1bmssIHdlIGhhdmUgYSBzZXQgb2YgY29udHJvbHMgZm9yIHRoZSBldmFsdWF0aW9uIGV4cGVyaW1lbnRzLiAKCisgKFQvRikgY3Jvc3MtdmFsaWRhdGlvbiBvbiB0aGUgdHJhaW5pbmcgc2V0CisgKFQvRikgcmV3ZWlnaHRpbmcgdGhlIHNhbXBsZXMgZm9yIHRyYWluaW5nIHNldCAKKyAobnVtYmVyKSBLLCB0aGUgbnVtYmVyIG9mIENWIGZvbGRzCisgKFQvRikgcHJvY2VzcyBmZWF0dXJlcyBmb3IgdHJhaW5pbmcgc2V0CisgKFQvRikgcnVuIGV2YWx1YXRpb24gb24gYW4gaW5kZXBlbmRlbnQgdGVzdCBzZXQKKyAoVC9GKSBwcm9jZXNzIGZlYXR1cmVzIGZvciB0ZXN0IHNldAoKKyAoVC9GKSBydW4gaW1wcm92ZWQgZ2JtIG1vZGVsCisgKG51bWJlcikgZ2JtLm51bXRyZWVzLCB0aGUgbnVtYmVyIG9mIHRyZWVzIHRvIHVzZSBpbiBHQk0gYmFzZWxpbmUKKyAoVC9GKSByZXR1cm4gcG9seW5vbWlhbCBmZWF0dXJlcyBtYXRyaXggb25seQorIChUL0YpIGFkZCBwb2x5bm9taWFsIGZlYXR1cmVzIHRvIHN0YXJ0ZXIgY29kZSBmZWF0dXJlcyBtYXRyaXgKCisgKFQvRikgcnVuIHN2bSBtb2RlbAorIChUL0YpIHBlcmZvcm0gbW9kZWwgc2VsZWN0aW9uIG92ZXIgYSBsaXN0IG9mIHN2bSBtb2RlbHMKKyAoVC9GKSBydW4gZXZhbHVhdGlvbiBvbiB0aGUgdGVzdCBzZXQKCisgKFQvRikgcnVuIHJhbmRvbSBmb3Jlc3QgbW9kZWwKKyAoVC9GKSByZWJhbGFuY2UgdHJhaW5pbmcgc2V0CisgKFQvRikgdHJhaW4gcmFuZG9tIGZvcmVzdCBtb2RlbAorIChUL0YpIHJ1biBldmFsdWF0aW9uIG9uIHRoZSB0ZXN0IHNldAorIChUL0YpIHR1bmUgaHlwZXJwYXJhbWV0ZXJzIGZvciByYW5kb20gZm9yZXN0IG1vZGVsCgorIChUL0YpIHJ1biByaWRnZSBtb2RlbAorICgwLzEpIGFscGhhLCBhbHBoYT0wIGZvciByaWRnZSByZWdyZXNzaW9uLCBhbHBoYT0xIGZvciBsYXNzbyByZWdyZXNzaW9uCisgKFQvRikgdHJhaW4gcmlkZ2UgbW9kZWwKCisgKFQvRikgcnVuIFBDQStMREEgbW9kZWwKKyAoVC9GKSBydW4gZGlmZmVyZW50IHByaW5jaXBhbCBjb21wb25lbnRzCisgKFQvRikgcnVuIExEQSBvbiB0cmFpbmluZyBzZXQKKyAoVC9GKSBydW4gZXZhbHVhdGlvbiBvbiB0aGUgdGVzdCBzZXQKCmBgYHtyIGV4cF9zZXR1cH0KcnVuLnByZXNlbnRhdGlvbi5kYXkgPC0gVFJVRSAjcHJlc2VudGF0aW9uIGRheSBmbGFnLiBObyB0cmFpbmluZy4gR2VuZXJhdGUgYSBjc3YgZmlsZQpydW4uY3YgPC0gVFJVRSAjIHJ1biBjcm9zcy12YWxpZGF0aW9uIG9uIHRoZSB0cmFpbmluZyBzZXQKc2FtcGxlLnJld2VpZ2h0IDwtIFRSVUUgIyBydW4gc2FtcGxlIHJld2VpZ2h0aW5nIGluIG1vZGVsIHRyYWluaW5nCksgPC0gNSAgIyBudW1iZXIgb2YgQ1YgZm9sZHMKcnVuLmZlYXR1cmUudHJhaW4gPC0gVFJVRSAjIHByb2Nlc3MgZmVhdHVyZXMgZm9yIHRyYWluaW5nIHNldApydW4udGVzdCA8LSBUUlVFICMgcnVuIGV2YWx1YXRpb24gb24gYW4gaW5kZXBlbmRlbnQgdGVzdCBzZXQKcnVuLmZlYXR1cmUudGVzdCA8LSBUUlVFICMgcHJvY2VzcyBmZWF0dXJlcyBmb3IgdGVzdCBzZXQKCiMgZ2JtCnJ1bi5nYm0udHJhaW4gPC0gVFJVRSAjIGdibShpbXJvdmVkKSBpcyB0aGUgY2hvc2VuIGFkdmFuY2VkIG1vZGVsCnJ1bi5nYm0udGVzdCA8LSBUUlVFICMgZ2JtKGltcm92ZWQpIGlzIHRoZSBjaG9zZW4gYWR2YW5jZWQgbW9kZWwKZ2JtLm51bXRyZWVzIDwtIDEwMDAgI251bWJlciBvZiB0cmVlcyB0byB1c2UgaW4gZ2JtCgojZmVhdHVyZXMgb3B0aW9ucwpydW4ucG9seS5mZWF0dXJlIDwtIFRSVUUgIyBwcm9jZXNzIHBvbHkgZmVhdHVyZXMKcnVuLmFkZC5wb2x5LmZlYXR1cmUgPC0gVFJVRSAjIGFuZCBwb2x5IGZlYXR1cmVzIHRvIGZlYXR1cmVzIG1hdHJpeAoKIyBzdm0KcnVuLnN2bSA8LSBGQUxTRSAjIHN2bSBpcyB0aGUgY2hvc2VuIGFkdmFuY2VkIG1vZGVsCm1vZGVsLnNlbGVjdGlvbiA8LSBUUlVFICMgcGVyZm9ybSBtb2RlbCBzZWxlY3Rpb24gb24gc3ZtIG1vZGVscwpydW4uc3ZtLnRlc3QgPC0gVFJVRSAjIGV2YWx1YXRlIHBlcmZvcm1hbmNlIG9uIHRoZSB0ZXN0IHNldAoKIyByYW5kb20gZm9yZXN0CnJ1bi5yZiA8LSBGQUxTRSAjIHJhbmRvbSBmb3Jlc3QgaXMgdGhlIGNob3NlbiBhZHZhbmNlZCBtb2RlbApydW4uYmFsYW5jZWQuZGF0YSA8LSBUUlVFICMgd2hldGhlciBvciBub3QgYmFsYW5jZSB0aGUgZGF0YQp0cmFpbi5yYW5kb20uZm9yZXN0IDwtIEZBTFNFICMgdHJhaW4gcmFuZG9tIGZvcmVzdCBtb2RlbAp0ZXN0LnJhbmRvbS5mb3Jlc3QgPC0gVFJVRSAjIHRlc3QgcmFuZG9tIGZvcmVzdCBtb2RlbAp0dW5lLnJhbmRvbS5mb3Jlc3QgPC0gRkFMU0UgIyB0dW5lIHJhbmRvbSBmb3Jlc3QgbW9kZWwKCiMgcmlkZ2UKcnVuLnJpZGdlIDwtIEZBTFNFICMgcmlkZ2UgaXMgdGhlIGNob3NlbiBhZHZhbmNlZCBtb2RlbAphbHBoYSA8LSAwICMgcmlkZ2UgcmVncmVzc2lvbgp0cmFpbi5yaWRnZSA8LSBUUlVFICMgdHJhaW4gcmlkZ2UgbW9kZWwKCiMgUENBICsgTERBCnJ1bi5wY2FfbGRhIDwtIEZBTFNFICMgUENBICsgTERBIGlzIHRoZSBjaG9zZW4gYWRjYW5jZWQgbW9kZWwKcnVuLnNlbGVjdF9QQyA8LSBUUlVFICNydW4gZGlmZmVyZW50IFBDcwpydW4ubGRhIDwtIFRSVUUgIyBydW4gbGRhIG9uIHRoZSB0cmFpbmluZyBzZXQKcnVuLnBjYV9sYWQudGVzdCA8LSBUUlVFICMgZXZhbHVhdGUgcGVyZm9ybWFuY2Ugb24gdGhlIHRlc3Qgc2V0CmBgYAoKVXNpbmcgY3Jvc3MtdmFsaWRhdGlvbiBvciBpbmRlcGVuZGVudCB0ZXN0IHNldCBldmFsdWF0aW9uLCB3ZSBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBvZiBtb2RlbHMgd2l0aCBkaWZmZXJlbnQgc3BlY2lmaWNhdGlvbnMuIAoKIyMjIFN0ZXAgMjogaW1wb3J0IGRhdGEgYW5kIHRyYWluLXRlc3Qgc3BsaXQgCmBgYHtyfQojdHJhaW4tdGVzdCBzcGxpdAppbmZvIDwtIHJlYWQuY3N2KHRyYWluX2xhYmVsX3BhdGgpCm4gPC0gbnJvdyhpbmZvKSAjZ2V0IG51bWJlciBvZiByb3dzIGZyb20gY3N2Cm5fdHJhaW4gPC0gcm91bmQobiooNC81KSwgMCkgI3VzZSA0LzUgYW1vdW50IG9mIGRhdGEgZm9yIHRyYWluaW5nCnRyYWluX2lkeCA8LSBzYW1wbGUoaW5mbyRJbmRleCwgbl90cmFpbiwgcmVwbGFjZSA9IEYpICNncmFiIGluZGV4ZXMgdXNlZCBmb3IgdHJhaW5pbmcKdGVzdF9pZHggPC0gc2V0ZGlmZihpbmZvJEluZGV4LCB0cmFpbl9pZHgpICMgZ2V0IGluZGV4ZXMgbm90IHVzZWQgZm9yIHRyYWluaW5nCmBgYAoKRmlkdWNpYWwgcG9pbnRzIGFyZSBzdG9yZWQgaW4gbWF0bGFiIGZvcm1hdC4gSW4gdGhpcyBzdGVwLCB3ZSByZWFkIHRoZW0gYW5kIHN0b3JlIHRoZW0gaW4gYSBsaXN0LgoKYGBge3IgcmVhZCBmaWR1Y2lhbCBwb2ludHN9CiNmdW5jdGlvbiB0byByZWFkIGZpZHVjaWFsIHBvaW50cwojaW5wdXQ6IGluZGV4CiNvdXRwdXQ6IG1hdHJpeCBvZiBmaWR1Y2lhbCBwb2ludHMgY29ycmVzcG9uZGluZyB0byB0aGUgaW5kZXgKbl9maWxlcyA8LSBsZW5ndGgobGlzdC5maWxlcyh0cmFpbl9pbWFnZV9kaXIsJypqcGcnKSkKcmVhZE1hdC5tYXRyaXggPC0gZnVuY3Rpb24oaW5kZXgpewogICAgIHJldHVybihyb3VuZChyZWFkTWF0KHBhc3RlMCh0cmFpbl9wdF9kaXIsIHNwcmludGYoIiUwNGQiLCBpbmRleCksICIubWF0IikpW1sxXV0sMCkpCn0KCmlmIChydW4ucHJlc2VudGF0aW9uLmRheSl7CiAgdGVzdF9pZHggPC0gYygxOm5fZmlsZXMpICNzYW1wbGUobl9maWxlcywgbl9maWxlcywgcmVwbGFjZSA9IEYpCiAgcnVuLmdibS50cmFpbiA8LSBGQUxTRQogIHJ1bi5mZWF0dXJlLnRyYWluIDwtIEZBTFNFCiAgcnVuLmdibS50ZXN0IDwtIFRSVUUKfQoKI2xvYWQgZmlkdWNpYWwgcG9pbnRzCmZpZHVjaWFsX3B0X2xpc3QgPC0gbGFwcGx5KDE6bl9maWxlcywgcmVhZE1hdC5tYXRyaXgpCnNhdmUoZmlkdWNpYWxfcHRfbGlzdCwgZmlsZT0iLi4vb3V0cHV0L2ZpZHVjaWFsX3B0X2xpc3QuUkRhdGEiKQpgYGAKCiMjIyBTdGVwIDM6IGNvbnN0cnVjdCBmZWF0dXJlcyBhbmQgcmVzcG9uc2VzCgorIFRoZSBmb2xsb3cgcGxvdHMgc2hvdyBob3cgcGFpcndpc2UgZGlzdGFuY2UgYmV0d2VlbiBmaWR1Y2lhbCBwb2ludHMgY2FuIHdvcmsgYXMgZmVhdHVyZSBmb3IgZmFjaWFsIGVtb3Rpb24gcmVjb2duaXRpb24uCgogICsgSW4gdGhlIGZpcnN0IGNvbHVtbiwgNzggZmlkdWNpYWxzIHBvaW50cyBvZiBlYWNoIGVtb3Rpb24gYXJlIG1hcmtlZCBpbiBvcmRlci4gCiAgKyBJbiB0aGUgc2Vjb25kIGNvbHVtbiBkaXN0cmlidXRpb25zIG9mIHZlcnRpY2FsIGRpc3RhbmNlIGJldHdlZW4gcmlnaHQgcHVwaWwoMSkgYW5kICByaWdodCBicm93IHBlYWsoMjEpIGFyZSBzaG93biBpbiAgaGlzdG9ncmFtcy4gRm9yIGV4YW1wbGUsIHRoZSBkaXN0YW5jZSBvZiBhbiBhbmdyeSBmYWNlIHRlbmRzIHRvIGJlIHNob3J0ZXIgdGhhbiB0aGF0IG9mIGEgc3VycHJpc2VkIGZhY2UuCiAgKyBUaGUgdGhpcmQgY29sdW1uIGlzIHRoZSBkaXN0cmlidXRpb25zIG9mIHZlcnRpY2FsIGRpc3RhbmNlcyBiZXR3ZWVuIHJpZ2h0IG1vdXRoIGNvcm5lcig1MCkKYW5kIHRoZSBtaWRwb2ludCBvZiB0aGUgdXBwZXIgbGlwKDUyKS4gIEZvciBleGFtcGxlLCB0aGUgZGlzdGFuY2Ugb2YgYW4gaGFwcHkgZmFjZSB0ZW5kcyB0byBiZSBzaG9ydGVyIHRoYW4gdGhhdCBvZiBhIHNhZCBmYWNlLgoKIVtGaWd1cmUxXSguLi9maWdzL2ZlYXR1cmVfdmlzdWFsaXphdGlvbi5qcGcpCgpgZmVhdHVyZS5SYCBpcyB0aGUgd3JhcHBlciBmb3IgYWxsIGZlYXR1cmUgZW5naW5lZXJpbmcgZnVuY3Rpb25zIGFuZCBvcHRpb25zLiBUaGUgZnVuY3Rpb24gYGZlYXR1cmUoIClgIGhhdmUgb3B0aW9ucyB0aGF0IGNvcnJlc3BvbmQgdG8gZGlmZmVyZW50IHNjZW5hcmlvcyBmb3IgdGhlIHByb2plY3QgYW5kIHByb2R1Y2VzIGFuIFIgb2JqZWN0IHRoYXQgY29udGFpbnMgZmVhdHVyZXMgYW5kIHJlc3BvbnNlcyB0aGF0IGFyZSByZXF1aXJlZCBieSBhbGwgdGhlIG1vZGVscyB0aGF0IGFyZSBnb2luZyB0byBiZSBldmFsdWF0ZWQgbGF0ZXIuIAogIAogICsgYGZlYXR1cmUuUmAKICArIElucHV0OiBsaXN0IG9mIGltYWdlcyBvciBmaWR1Y2lhbCBwb2ludAogICsgT3V0cHV0OiBhbiBSRGF0YSBmaWxlIHRoYXQgY29udGFpbnMgZXh0cmFjdGVkIGZlYXR1cmVzIGFuZCBjb3JyZXNwb25kaW5nIHJlc3BvbnNlcwoKYGBge3IgZmVhdHVyZX0Kc291cmNlKCIuLi9saWIvZmVhdHVyZS5SIikKdG1fZmVhdHVyZV90cmFpbiA8LSBOQQpnYm1fdG1fZmVhdHVyZV90cmFpbiA8LSBOQQppZihydW4uZmVhdHVyZS50cmFpbil7CiAgdG1fZmVhdHVyZV90cmFpbiA8LSBzeXN0ZW0udGltZShkYXRfdHJhaW48LWZlYXR1cmUoZmlkdWNpYWxfcHRfbGlzdCx0cmFpbl9pZHgsIHJ1bi5wb2x5LmZlYXR1cmUsIHJ1bi5hZGQucG9seS5mZWF0dXJlKSkKICBnYm1fdG1fZmVhdHVyZV90cmFpbiA8LSBzeXN0ZW0udGltZShnYm1fZGF0X3RyYWluPC1mZWF0dXJlKGZpZHVjaWFsX3B0X2xpc3QsdHJhaW5faWR4LCBGQUxTRSwgRkFMU0UpKQogIHNhdmUoZ2JtX2RhdF90cmFpbiwgZmlsZT0iLi4vb3V0cHV0L2dibV9mZWF0dXJlX3RyYWluLlJEYXRhIikKICBzYXZlKGRhdF90cmFpbiwgZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfdHJhaW4uUkRhdGEiKQp9ZWxzZXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluLlJEYXRhIikKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9nYm1fZmVhdHVyZV90cmFpbi5SRGF0YSIpCn0KCnRtX2ZlYXR1cmVfdGVzdCA8LSBOQQpnYm1fdG1fZmVhdHVyZV90ZXN0IDwtIE5BCmlmKHJ1bi5mZWF0dXJlLnRlc3QpewogIHRtX2ZlYXR1cmVfdGVzdCA8LSBzeXN0ZW0udGltZShkYXRfdGVzdCA8LSBmZWF0dXJlKGZpZHVjaWFsX3B0X2xpc3QsIHRlc3RfaWR4LCBydW4ucG9seS5mZWF0dXJlLCBydW4uYWRkLnBvbHkuZmVhdHVyZSkpCiAgZ2JtX3RtX2ZlYXR1cmVfdGVzdCA8LSBzeXN0ZW0udGltZShnYm1fZGF0X3Rlc3QgPC0gZmVhdHVyZShmaWR1Y2lhbF9wdF9saXN0LCB0ZXN0X2lkeCwgRkFMU0UsIEZBTFNFKSkKICBzYXZlKGdibV9kYXRfdGVzdCwgZmlsZT0iLi4vb3V0cHV0L2dibV9mZWF0dXJlX3Rlc3QuUkRhdGEiKQogIHNhdmUoZGF0X3Rlc3QsIGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3Rlc3QuUkRhdGEiKQp9ZWxzZXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3Rlc3QuUkRhdGEiKQogIGxvYWQoZmlsZT0iLi4vb3V0cHV0L2dibV9mZWF0dXJlX3Rlc3QuUkRhdGEiKQp9CmBgYAoKIyMjIFN0ZXAgNDogdHJhaW4gY2xhc3NpZmljYXRpb24gbW9kZWxzIHdpdGggdHJhaW5pbmcgZmVhdHVyZXMgYW5kIHJlc3BvbnNlczsgcnVuIHRlc3Qgb24gdGVzdCBpbWFnZXMKCkNhbGwgdGhlIHRyYWluIG1vZGVsIGFuZCB0ZXN0IG1vZGVsIGZyb20gbGlicmFyeS4gCgpgdHJhaW4uUmAgYW5kIGB0ZXN0LlJgIGFyZSB3cmFwcGVycyBmb3IgYWxsIG1vZGVsIHRyYWluaW5nIHN0ZXBzIGFuZCBjbGFzc2lmaWNhdGlvbi9wcmVkaWN0aW9uIHN0ZXBzLiAKCisgYHRyYWluLlJgCiAgKyBJbnB1dDogYSBkYXRhIGZyYW1lIGNvbnRhaW5pbmcgZmVhdHVyZXMgYW5kIGxhYmVscyBhbmQgYSBwYXJhbWV0ZXIgbGlzdC4KICArIE91dHB1dDphIHRyYWluZWQgbW9kZWwKKyBgdGVzdC5SYAogICsgSW5wdXQ6IHRoZSBmaXR0ZWQgY2xhc3NpZmljYXRpb24gbW9kZWwgdXNpbmcgdHJhaW5pbmcgZGF0YSBhbmQgcHJvY2Vzc2VkIGZlYXR1cmVzIGZyb20gdGVzdGluZyBpbWFnZXMgCiAgKyBJbnB1dDogYW4gUiBvYmplY3QgdGhhdCBjb250YWlucyBhIHRyYWluZWQgY2xhc3NpZmllci4KICArIE91dHB1dDogdHJhaW5pbmcgbW9kZWwgc3BlY2lmaWNhdGlvbgoKKyBJbiB0aGlzIFN0YXJ0ZXIgQ29kZSwgd2UgdXNlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gd2l0aCBMQVNTTyBwZW5hbHR5IHRvIGRvIGNsYXNzaWZpY2F0aW9uLiAKCmBgYHtyIGxvYWRsaWJ9CnNvdXJjZSgiLi4vbGliL3RyYWluLlIiKSAKc291cmNlKCIuLi9saWIvdGVzdC5SIikKYGBgCgojIEJhc2VsaW5lIE1vZGVsCgoKLS0tLS0tLS1USElTIElTIFRPIFNFUEFSQVRFIEVBQ0ggTU9ERUwuIFRISVMgSVMgVE8gU0VQQVJBVEUgRUFDSCBNT0RFTC4gVEhJUyBJUyBUTyBTRVBBUkFURSBFQUNIIE1PREVMLi0tLS0tLS0tLS0KCgoKCgojIEFkdmFuY2VkIE1vZGVsIDE6IEltcHJvdmVkIEdCTSBNb2RlbAoKKiBNb2RlbCBUcmFpbmluZwoKYGBge3J9CmlmIChydW4uZ2JtLnRyYWluKXsKICBpZiAoc2FtcGxlLnJld2VpZ2h0KXsKICAgIAogICAgZ2JtX2RhdF90cmFpbiRsYWJlbCA8LSBhcy5mYWN0b3IoZ2JtX2RhdF90cmFpbiRsYWJlbCkKICAgIGRhdF90cmFpbl9iYWxhbmNlZF9nYm0gPC0gU01PVEUobGFiZWwgfiAuLCBnYm1fZGF0X3RyYWluLCBwZXJjLm92ZXIgPSAxMDAsIHBlcmMudW5kZXI9MjAwKQogICAgI3NhdmUoZGF0X3RyYWluX2JhbGFuY2VkX3Jvc2UsIGZpbGU9Ii4uL291dHB1dC9iYWxhbmNlZF9kYXRhLlJEYXRhIikKICAgIHRhYmxlKGRhdF90cmFpbl9iYWxhbmNlZF9nYm0kbGFiZWwpCiAgICAKICAgIGdibV90bV90cmFpbiA8LSBzeXN0ZW0udGltZShnYm1fdHJhaW4gPC0gdHJhaW5fZ2JtKGRhdF90cmFpbl9iYWxhbmNlZF9nYm0sIHM9MC4xLCBLPUssIG49Z2JtLm51bXRyZWVzLHcgPSBOVUxMKSkKICAgIAogIH0gZWxzZSB7CiAgICBnYm1fdG1fdHJhaW4gPC0gc3lzdGVtLnRpbWUoZ2JtX3RyYWluIDwtIHRyYWluX2dibShnYm1fZGF0X3RyYWluLCBzPTAuMSwgSz1LLCBuPWdibS5udW10cmVlcyx3ID0gTlVMTCkpCiAgfQogIAogICMgcGxvdCB0aGUgcGVyZm9ybWFuY2UKICBiZXN0Lml0ZXIub29iIDwtIGdibS5wZXJmKGdibV90cmFpbixtZXRob2Q9Ik9PQiIpICAjIHJldHVybnMgb3V0LW9mLWJhZyBlc3RpbWF0ZWQgYmVzdCBudW1iZXIgb2YgdHJlZXMKICBwcmludChiZXN0Lml0ZXIub29iKQogIGJlc3QuaXRlci5jdiA8LSBnYm0ucGVyZihnYm1fdHJhaW4sbWV0aG9kPSJjdiIpICAgIyByZXR1cm5zIEstZm9sZCBjdiBlc3RpbWF0ZSBvZiBiZXN0IG51bWJlciBvZiB0cmVlcwogIHByaW50KGJlc3QuaXRlci5jdikKICAKICBzYXZlUkRTKGdibV90cmFpbiwgIi4uL291dHB1dC9nYm1fbW9kZWwucmRzIikKICBzYXZlKGdibV90bV90cmFpbiwgYmVzdC5pdGVyLmN2LCBmaWxlPSIuLi9vdXRwdXQvZ2JtX291dHB1dHMuUkRhdGEiKQp9CgpgYGAKCiogRXZhbHVhdGlvbiBvbiBUZXN0IFNldAoKYGBge3J9CmlmKHJ1bi5nYm0udGVzdCl7CiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZ2JtX291dHB1dHMuUkRhdGEiKQogIGdibV90bV90ZXN0ID0gTkEKICBmZWF0dXJlX3Rlc3QgPC0gYXMubWF0cml4KGdibV9kYXRfdGVzdFssIDE6bmNvbChnYm1fZGF0X3Rlc3QpLTFdKQogIAogIGdibV90cmFpbiA8LSByZWFkUkRTKCIuLi9vdXRwdXQvZ2JtX21vZGVsLnJkcyIpCiAgZ2JtX3RtX3Rlc3QgPC0gc3lzdGVtLnRpbWUocHJvYl9wcmVkX2Jhc2VsaW5lPC10ZXN0X2dibShnYm1fdHJhaW4sYXMuZGF0YS5mcmFtZShmZWF0dXJlX3Rlc3QpLG49YmVzdC5pdGVyLmN2LHByZWQudHlwZSA9ICdyZXNwb25zZScpKQogIAogIGxhYmVsX3ByZWRfYmFzZWxpbmUgPC0gY29sbmFtZXMocHJvYl9wcmVkX2Jhc2VsaW5lKVthcHBseShwcm9iX3ByZWRfYmFzZWxpbmUsIDEsIHdoaWNoLm1heCldCn0KYGBgCgpzaG93IGdibSBhY2N1cmFjeSBhbmQgQVVDCmBgYHtyfQppZiAocnVuLmdibS50ZXN0KXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9nYm1fb3V0cHV0cy5SRGF0YSIpCiAgZ2JtX2FjY3UgPC0gbWVhbihnYm1fZGF0X3Rlc3QkbGFiZWwgPT0gbGFiZWxfcHJlZF9iYXNlbGluZSkKICBnYm0uYXVjIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMobGFiZWxfcHJlZF9iYXNlbGluZSksIGdibV9kYXRfdGVzdCRsYWJlbCkKICBnYm1fYXVjID0gV2VpZ2h0ZWRBVUMoZ2JtLmF1YykKICBjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyBnYm0gdHJhaW5pbmcgZmVhdHVyZXM9IiwgZ2JtX3RtX2ZlYXR1cmVfdHJhaW5bMV0sICJzIFxuIikKICBjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyBnYm0gdGVzdGluZyBmZWF0dXJlcz0iLCBnYm1fdG1fZmVhdHVyZV90ZXN0WzFdLCAicyBcbiIpCiAgY2F0KCJUaGUgQVVDIG9mIGdibSBtb2RlbCBpcyIsIGdibV9hdWMsICIuXG4iKQogIGNhdCgiVGhlIGFjY3VyYWN5IG9mIEdCTSBiYXNlbGluZSBtb2RlbCBpcyIsIGdibV9hY2N1KjEwMCwgIiUuXG4iKQogIGNhdCgiVGltZSBmb3IgdHJhaW5pbmcgZ2JtIG1vZGVsPSIsIGdibV90bV90cmFpblsxXSwgInMgXG4iKSAKICBjYXQoIlRpbWUgZm9yIHRlc3RpbmcgbW9kZWw9IiwgZ2JtX3RtX3Rlc3RbMV0sICJzIFxuIikKfQpgYGAKLS0tLS0tLS1USElTIElTIFRPIFNFUEFSQVRFIEVBQ0ggTU9ERUwuIFRISVMgSVMgVE8gU0VQQVJBVEUgRUFDSCBNT0RFTC4gVEhJUyBJUyBUTyBTRVBBUkFURSBFQUNIIE1PREVMLi0tLS0tLS0tLS0KCgojIEFkdmFuY2VkIE1vZGVsIDI6IFJhbmRvbSBGb3Jlc3QKCiogQmFsYW5jZSBUcmFpbiBTZXQKCmBgYHtyfQppZihydW4ucmYpewogICMgdHJhbnNmZXIgbGFiZWwgY29sdW1uIGZyb20gZmFjdG9yIHRvIG51bWVyaWMKICBkYXRfdHJhaW4kbGFiZWwgPC0gYXMubnVtZXJpYyhkYXRfdHJhaW4kbGFiZWwpCiAgZGF0X3Rlc3QkbGFiZWwgPC0gYXMubnVtZXJpYyhkYXRfdGVzdCRsYWJlbCkKCiAgaWYocnVuLmJhbGFuY2VkLmRhdGEpewogICAgZGF0X3RyYWluX2JhbGFuY2VkX3Jvc2U8LVJPU0UobGFiZWx+LiwgZGF0X3RyYWluLHNlZWQ9MjAyMCkkZGF0YQogICAgc2F2ZShkYXRfdHJhaW5fYmFsYW5jZWRfcm9zZSwgZmlsZT0iLi4vb3V0cHV0L2JhbGFuY2VkX3RyYWluX2RhdGFfcm9zZS5SRGF0YSIpCiAgICBkYXRfdGVzdF9iYWxhbmNlZF9yb3NlIDwtIFJPU0UobGFiZWx+LiwgZGF0X3Rlc3QsIHNlZWQ9MjAyMCkkZGF0YQogICAgc2F2ZShkYXRfdGVzdF9iYWxhbmNlZF9yb3NlLCBmaWxlID0gIi4uL291dHB1dC9iYWxhbmNlZF90ZXN0X2RhdGFfcm9zZS5SRGF0YSIpCiAgfSBlbHNlIHsKICAgIGxvYWQoZmlsZSA9ICIuLi9vdXRwdXQvYmFsYW5jZWRfdHJhaW5fZGF0YV9yb3NlLlJEYXRhIikKICAgIGxvYWQoZmlsZSA9ICIuLi9vdXRwdXQvYmFsYW5jZWRfdGVzdF9kYXRhX3Jvc2UuUkRhdGEiKQogIH0KICB0YWJsZShkYXRfdHJhaW5fYmFsYW5jZWRfcm9zZSRsYWJlbCkKICB0YWJsZShkYXRfdGVzdF9iYWxhbmNlZF9yb3NlJGxhYmVsKQp9CmBgYAoKCiogVHVuZSBQYXJhbWV0ZXJzIGZvciBSYW5kb20gRm9yZXN0CgpgYGB7cn0KaWYocnVuLnJmKXsKICBzb3VyY2UoIi4uL2xpYi9yYW5kb21fZm9yZXN0X29sZF9mZWF0dXJlLlIiKQogIGlmKHR1bmUucmFuZG9tLmZvcmVzdCl7CiAgICB0aW1lLnJmLnR1bmUgPC0gc3lzdGVtLnRpbWUocmYudHVuZSA8LSByYW5kb21fZm9yZXN0X3R1bmUoZGF0X3RyYWluX2JhbGFuY2VkX3Jvc2UpKQogICAgc2F2ZShyZi50dW5lLCBmaWxlPSIuLi9vdXRwdXQvcmZfdHVuZS5SRGF0YSIpCiAgICBzYXZlKHRpbWUucmYudHVuZSwgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHVuZV90aW1lLlJEYXRhIikKICB9ZWxzZXsKICAgIGxvYWQoIi4uL291dHB1dC9yZl90dW5lLlJEYXRhIikKICAgIGxvYWQoIi4uL291dHB1dC9yZl90dW5lX3RpbWUuUkRhdGEiKQogIH0KICByZi50dW5lCiAgdGltZS5yZi50dW5lWzFdCn0KYGBgCgptdHJ5ID0gMzA4IGlzIHRoZSBiZXN0LgoKKiBUdW5lIFRyZWVzIGFuZCBOb2RlcwoKYGBge3J9CmlmKHJ1bi5yZil7CiAgc291cmNlKCIuLi9saWIvcmFuZG9tX2ZvcmVzdF9vbGRfZmVhdHVyZS5SIikKICBpZih0dW5lLnJhbmRvbS5mb3Jlc3QpewogICAgIyBUcmFpbiA1MDAgdHJlZXM6CiAgICB0aW1lLnJmLnRyYWluLnRyZWU1MDAgPC0gc3lzdGVtLnRpbWUocmFuZG9tX2ZvcmVzdF9maXRfNTAwX3RyZWVzIDwtCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByYW5kb21fZm9yZXN0X3RyYWluXzUwMChkYXRfdHJhaW5fYmFsYW5jZWRfcm9zZSxtdHJ5ID0gMzA4KSkKICAgIHNhdmUocmFuZG9tX2ZvcmVzdF9maXRfNTAwX3RyZWVzLCBmaWxlID0gIi4uL291dHB1dC9yZl90cmFpbl81MDBfdHJlZXMuUkRhdGEiKQogICAgc2F2ZSh0aW1lLnJmLnRyYWluLnRyZWU1MDAsIGZpbGUgPSAiLi4vb3V0cHV0L3JmX3RyYWluXzUwMF90cmVlc190aW1lLlJEYXRhIikKICAgIGNhdCgiNTAwIHRyZWUgdGltZSIsIHRpbWUucmYudHJhaW4udHJlZTUwMCkKICAgICMgVHJhaW4gMTAwMCB0cmVlczoKICAgIHRpbWUucmYudHJhaW4udHJlZTEwMDAgPC0gc3lzdGVtLnRpbWUocmFuZG9tX2ZvcmVzdF9maXRfMTAwMF90cmVlcyA8LQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhbmRvbV9mb3Jlc3RfdHJhaW5fMTAwMChkYXRfdHJhaW5fYmFsYW5jZWRfcm9zZSxtdHJ5ID0gMzA4KSkKICAgIHNhdmUocmFuZG9tX2ZvcmVzdF9maXRfMTAwMF90cmVlcywgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fMTAwMF90cmVlcy5SRGF0YSIpCiAgICBzYXZlKHRpbWUucmYudHJhaW4udHJlZTEwMDAsIGZpbGUgPSAiLi4vb3V0cHV0L3JmX3RyYWluXzEwMDBfdHJlZXNfdGltZS5SRGF0YSIpCiAgICBjYXQoIjEwMDAgdHJlZSB0aW1lIiwgdGltZS5yZi50cmFpbi50cmVlMTAwMCkKICAgICMgVHJhaW4gMTUwMCB0cmVlczoKICAgIHRpbWUucmYudHJhaW4udHJlZTE1MDAgPC0gc3lzdGVtLnRpbWUocmFuZG9tX2ZvcmVzdF9maXRfMTUwMF90cmVlcyA8LQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhbmRvbV9mb3Jlc3RfdHJhaW5fMTUwMChkYXRfdHJhaW5fYmFsYW5jZWRfcm9zZSxtdHJ5ID0gMzA4KSkKICAgIHNhdmUocmFuZG9tX2ZvcmVzdF9maXRfMTUwMF90cmVlcywgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fMTUwMF90cmVlcy5SRGF0YSIpCiAgICBzYXZlKHRpbWUucmYudHJhaW4udHJlZTE1MDAsIGZpbGUgPSAiLi4vb3V0cHV0L3JmX3RyYWluXzE1MDBfdHJlZXNfdGltZS5SRGF0YSIpCiAgICBjYXQoIjE1MDAgdHJlZSB0aW1lIiwgdGltZS5yZi50cmFpbi50cmVlMTUwMCkKICAgICMgVHJhaW4gMjAwMCB0cmVlczoKICAgIHRpbWUucmYudHJhaW4udHJlZTIwMDAgPC0gc3lzdGVtLnRpbWUocmFuZG9tX2ZvcmVzdF9maXRfMjAwMF90cmVlcyA8LQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhbmRvbV9mb3Jlc3RfdHJhaW5fMjAwMChkYXRfdHJhaW5fYmFsYW5jZWRfcm9zZSxtdHJ5ID0gMzA4KSkKICAgIHNhdmUocmFuZG9tX2ZvcmVzdF9maXRfMjAwMF90cmVlcywgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fMjAwMF90cmVlcy5SRGF0YSIpCiAgICBzYXZlKHRpbWUucmYudHJhaW4udHJlZTIwMDAsIGZpbGUgPSAiLi4vb3V0cHV0L3JmX3RyYWluXzIwMDBfdHJlZXNfdGltZS5SRGF0YSIpCiAgICBjYXQoIjIwMDAgdHJlZSB0aW1lIiwgdGltZS5yZi50cmFpbi50cmVlMjAwMCkKICAgICMgVHJhaW4gMjUwMCB0cmVlczoKICAgIHRpbWUucmYudHJhaW4udHJlZTI1MDAgPC0gc3lzdGVtLnRpbWUocmFuZG9tX2ZvcmVzdF9maXRfMjUwMF90cmVlcyA8LQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhbmRvbV9mb3Jlc3RfdHJhaW5fMjUwMChkYXRfdHJhaW5fYmFsYW5jZWRfcm9zZSxtdHJ5ID0gMzA4KSkKICAgIHNhdmUocmFuZG9tX2ZvcmVzdF9maXRfMjUwMF90cmVlcywgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fMjUwMF90cmVlcy5SRGF0YSIpCiAgICBzYXZlKHRpbWUucmYudHJhaW4udHJlZTI1MDAsIGZpbGUgPSAiLi4vb3V0cHV0L3JmX3RyYWluXzI1MDBfdHJlZXNfdGltZS5SRGF0YSIpCiAgICBjYXQoIjI1MDAgdHJlZSB0aW1lIiwgdGltZS5yZi50cmFpbi50cmVlMjUwMCkKCiAgICAjIFRyYWluIDEwIG5vZGVzCiAgICB0aW1lLnJmLnRyYWluLm5vZGUxMCA8LSBzeXN0ZW0udGltZShyYW5kb21fZm9yZXN0X2ZpdF8xMF9ub2RlcyA8LQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByYW5kb21fZm9yZXN0X3RyYWluXzEwKGRhdF90cmFpbl9iYWxhbmNlZF9yb3NlLG10cnkgPSAzMDgpKQogICAgc2F2ZShyYW5kb21fZm9yZXN0X2ZpdF8xMF9ub2RlcywgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fMTBfbm9kZXMuUkRhdGEiKQogICAgc2F2ZSh0aW1lLnJmLnRyYWluLm5vZGUxMCwgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fMTBfbm9kZXNfdGltZS5SRGF0YSIpCiAgICBjYXQoIjEwIG5vZGUgdGltZSIsIHRpbWUucmYudHJhaW4ubm9kZTEwKQogICAgIyBUcmFpbiAxNSBub2RlcwogICAgdGltZS5yZi50cmFpbi5ub2RlMTUgPC0gc3lzdGVtLnRpbWUocmFuZG9tX2ZvcmVzdF9maXRfMTVfbm9kZXMgPC0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmFuZG9tX2ZvcmVzdF90cmFpbl8xNShkYXRfdHJhaW5fYmFsYW5jZWRfcm9zZSxtdHJ5ID0gMzA4KSkKICAgIHNhdmUocmFuZG9tX2ZvcmVzdF9maXRfMTVfbm9kZXMsIGZpbGUgPSAiLi4vb3V0cHV0L3JmX3RyYWluXzE1X25vZGVzLlJEYXRhIikKICAgIHNhdmUodGltZS5yZi50cmFpbi5ub2RlMTUsIGZpbGUgPSAiLi4vb3V0cHV0L3JmX3RyYWluXzE1X25vZGVzX3RpbWUuUkRhdGEiKQogICAgY2F0KCIxNSBub2RlIHRpbWUiLCB0aW1lLnJmLnRyYWluLm5vZGUxNSkKICAgICMgVHJhaW4gMjAgbm9kZXMKICAgIHRpbWUucmYudHJhaW4ubm9kZTIwIDwtIHN5c3RlbS50aW1lKHJhbmRvbV9mb3Jlc3RfZml0XzIwX25vZGVzIDwtCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhbmRvbV9mb3Jlc3RfdHJhaW5fMjAoZGF0X3RyYWluX2JhbGFuY2VkX3Jvc2UsbXRyeSA9IDMwOCkpCiAgICBzYXZlKHJhbmRvbV9mb3Jlc3RfZml0XzIwX25vZGVzLCBmaWxlID0gIi4uL291dHB1dC9yZl90cmFpbl8yMF9ub2Rlcy5SRGF0YSIpCiAgICBzYXZlKHRpbWUucmYudHJhaW4ubm9kZTIwLCBmaWxlID0gIi4uL291dHB1dC9yZl90cmFpbl8yMF9ub2Rlc190aW1lLlJEYXRhIikKICAgIGNhdCgiMjAgbm9kZSB0aW1lIiwgdGltZS5yZi50cmFpbi5ub2RlMjApCiAgICAjIFRyYWluIDI1IG5vZGVzCiAgICB0aW1lLnJmLnRyYWluLm5vZGUyNSA8LSBzeXN0ZW0udGltZShyYW5kb21fZm9yZXN0X2ZpdF8yNV9ub2RlcyA8LQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByYW5kb21fZm9yZXN0X3RyYWluXzI1KGRhdF90cmFpbl9iYWxhbmNlZF9yb3NlLG10cnkgPSAzMDgpKQogICAgc2F2ZShyYW5kb21fZm9yZXN0X2ZpdF8yNV9ub2RlcywgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fMjVfbm9kZXMuUkRhdGEiKQogICAgc2F2ZSh0aW1lLnJmLnRyYWluLm5vZGUyNSwgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fMjVfbm9kZXNfdGltZS5SRGF0YSIpCiAgICBjYXQoIjI1IG5vZGUgdGltZSIsIHRpbWUucmYudHJhaW4ubm9kZTI1KQogICAgIyBUcmFpbiAzMCBub2RlcwogICAgdGltZS5yZi50cmFpbi5ub2RlMzAgPC0gc3lzdGVtLnRpbWUocmFuZG9tX2ZvcmVzdF9maXRfMzBfbm9kZXMgPC0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmFuZG9tX2ZvcmVzdF90cmFpbl8zMChkYXRfdHJhaW5fYmFsYW5jZWRfcm9zZSxtdHJ5ID0gMzA4KSkKICAgIHNhdmUocmFuZG9tX2ZvcmVzdF9maXRfMzBfbm9kZXMsIGZpbGUgPSAiLi4vb3V0cHV0L3JmX3RyYWluXzMwX25vZGVzLlJEYXRhIikKICAgIHNhdmUodGltZS5yZi50cmFpbi5ub2RlMzAsIGZpbGUgPSAiLi4vb3V0cHV0L3JmX3RyYWluXzMwX25vZGVzX3RpbWUuUkRhdGEiKQogICAgY2F0KCIzMCBub2RlIHRpbWUiLCB0aW1lLnJmLnRyYWluLm5vZGUzMCkKICB9IGVsc2UgewogICAgbG9hZCgiLi4vb3V0cHV0L3JmX3RyYWluXzUwMF90cmVlcy5SRGF0YSIpCiAgICBsb2FkKCIuLi9vdXRwdXQvcmZfdHJhaW5fMTAwMF90cmVlcy5SRGF0YSIpCiAgICBsb2FkKCIuLi9vdXRwdXQvcmZfdHJhaW5fMTUwMF90cmVlcy5SRGF0YSIpCiAgICBsb2FkKCIuLi9vdXRwdXQvcmZfdHJhaW5fMjAwMF90cmVlcy5SRGF0YSIpCiAgICBsb2FkKCIuLi9vdXRwdXQvcmZfdHJhaW5fMjUwMF90cmVlcy5SRGF0YSIpCiAgICBsb2FkKCIuLi9vdXRwdXQvcmZfdHJhaW5fMTBfbm9kZXMuUkRhdGEiKQogICAgbG9hZCgiLi4vb3V0cHV0L3JmX3RyYWluXzE1X25vZGVzLlJEYXRhIikKICAgIGxvYWQoIi4uL291dHB1dC9yZl90cmFpbl8yMF9ub2Rlcy5SRGF0YSIpCiAgICBsb2FkKCIuLi9vdXRwdXQvcmZfdHJhaW5fMjVfbm9kZXMuUkRhdGEiKQogICAgbG9hZCgiLi4vb3V0cHV0L3JmX3RyYWluXzMwX25vZGVzLlJEYXRhIikKICB9CgoKCiAgI0Vycm9yIHJhdGUgb2YgZWFjaCBoeXBlcnBhcmFtZXRlcjoKCgoKICAjIEV2YWx1YXRlIGVhY2ggaHlwZXJwYXJhbWV0ZXIKCgogICMgUHJlZGljdGVkIHZhbHVlIGZyb20gNTAwIHRyZWVzJyBtb2RlbDoKICByZl9wcmVkaWN0ZWRfYmFsYW5jZWQgPC0gYXMubnVtZXJpYyhhcy52ZWN0b3IocmFuZG9tX2ZvcmVzdF90ZXN0KHJhbmRvbV9mb3Jlc3RfZml0XzUwMF90cmVlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UpKSkKICByZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCA8LSBhcy5udW1lcmljKGFzLnZlY3RvcihyYW5kb21fZm9yZXN0X3Rlc3QocmFuZG9tX2ZvcmVzdF9maXRfNTAwX3RyZWVzLCBkYXRfdGVzdCkpKQogICMgRXZhbHVhdGUgNTAwIHRyZWVzOgogIHJmX2FjY3VyYWN5X2JhbGFuY2VkIDwtIG1lYW4ocm91bmQocmZfcHJlZGljdGVkX2JhbGFuY2VkID09IGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UkbGFiZWwpKQogIHRwci5mcHIuYmFsYW5jZWQgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhyZl9wcmVkaWN0ZWRfYmFsYW5jZWQpLGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UkbGFiZWwpCiAgcmZfQVVDX2JhbGFuY2VkIDwtIFdlaWdodGVkQVVDKHRwci5mcHIuYmFsYW5jZWQpCiAgcmZfYWNjdXJhY3lfaW1iYWxhbmNlZCA8LSBtZWFuKHJvdW5kKHJmX3ByZWRpY3RlZF9pbWJhbGFuY2VkID09IGRhdF90ZXN0JGxhYmVsKSkKICB0cHIuZnByLmltYmFsYW5jZWQgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhyZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCksZGF0X3Rlc3QkbGFiZWwpCiAgcmZfQVVDX2ltYmFsYW5jZWQgPC0gV2VpZ2h0ZWRBVUModHByLmZwci5pbWJhbGFuY2VkKQoKICBjYXQoIkFjY3VyYWN5KGJhbGFuY2VkKSA1MDAiLCByZl9hY2N1cmFjeV9iYWxhbmNlZCoxMDAsIiUuXG4iKQogIGNhdCgiQVVDKGJhbGFuY2VkKSA1MDAiLCByZl9BVUNfYmFsYW5jZWQsIi5cbiIpCiAgY2F0KCJBY2N1cmFjeShpbWJhbGFuY2VkKSA1MDAiLCByZl9hY2N1cmFjeV9pbWJhbGFuY2VkKjEwMCwiJS5cbiIpCiAgY2F0KCJBVUMoaW1iYWxhbmNlZCkgNTAwIixyZl9BVUNfaW1iYWxhbmNlZCwiLlxuIikKCgogICMgRXZhbHVhdGlvbiBmcm9tIDEwMDAgdHJlZXMnIG1vZGVsOgogIHJmX3ByZWRpY3RlZF9iYWxhbmNlZCA8LSBhcy5udW1lcmljKGFzLnZlY3RvcihyYW5kb21fZm9yZXN0X3Rlc3QocmFuZG9tX2ZvcmVzdF9maXRfMTAwMF90cmVlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UpKSkKICByZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCA8LSBhcy5udW1lcmljKGFzLnZlY3RvcihyYW5kb21fZm9yZXN0X3Rlc3QocmFuZG9tX2ZvcmVzdF9maXRfMTAwMF90cmVlcywgZGF0X3Rlc3QpKSkKCiAgcmZfYWNjdXJhY3lfYmFsYW5jZWQgPC0gbWVhbihyb3VuZChyZl9wcmVkaWN0ZWRfYmFsYW5jZWQgPT0gZGF0X3Rlc3RfYmFsYW5jZWRfcm9zZSRsYWJlbCkpCiAgdHByLmZwci5iYWxhbmNlZCA8LSBXZWlnaHRlZFJPQyhhcy5udW1lcmljKHJmX3ByZWRpY3RlZF9iYWxhbmNlZCksZGF0X3Rlc3QkbGFiZWwpCiAgcmZfQVVDX2JhbGFuY2VkIDwtIFdlaWdodGVkQVVDKHRwci5mcHIuYmFsYW5jZWQpCiAgcmZfYWNjdXJhY3lfaW1iYWxhbmNlZCA8LSBtZWFuKHJvdW5kKHJmX3ByZWRpY3RlZF9pbWJhbGFuY2VkID09IGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UkbGFiZWwpKQogIHRwci5mcHIuaW1iYWxhbmNlZCA8LSBXZWlnaHRlZFJPQyhhcy5udW1lcmljKHJmX3ByZWRpY3RlZF9pbWJhbGFuY2VkKSxkYXRfdGVzdCRsYWJlbCkKICByZl9BVUNfaW1iYWxhbmNlZCA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLmltYmFsYW5jZWQpCgogIGNhdCgiQWNjdXJhY3koYmFsYW5jZWQpIDEwMDAiLCByZl9hY2N1cmFjeV9iYWxhbmNlZCoxMDAsIiUuXG4iKQogIGNhdCgiQVVDKGJhbGFuY2VkKSAxMDAwIiwgcmZfQVVDX2JhbGFuY2VkLCIuXG4iKQogIGNhdCgiQWNjdXJhY3koaW1iYWxhbmNlZCkgMTAwMCIsIHJmX2FjY3VyYWN5X2ltYmFsYW5jZWQqMTAwLCIlLlxuIikKICBjYXQoIkFVQyhpbWJhbGFuY2VkKSAxMDAwIixyZl9BVUNfaW1iYWxhbmNlZCwiLlxuIikKCiAgIyAxNTAwIHRyZWVzCiAgcmZfcHJlZGljdGVkX2JhbGFuY2VkIDwtIGFzLm51bWVyaWMoYXMudmVjdG9yKHJhbmRvbV9mb3Jlc3RfdGVzdChyYW5kb21fZm9yZXN0X2ZpdF8xNTAwX3RyZWVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0X3Rlc3RfYmFsYW5jZWRfcm9zZSkpKQogIHJmX3ByZWRpY3RlZF9pbWJhbGFuY2VkIDwtIGFzLm51bWVyaWMoYXMudmVjdG9yKHJhbmRvbV9mb3Jlc3RfdGVzdChyYW5kb21fZm9yZXN0X2ZpdF8xNTAwX3RyZWVzLCBkYXRfdGVzdCkpKQoKICByZl9hY2N1cmFjeV9iYWxhbmNlZCA8LSBtZWFuKHJvdW5kKHJmX3ByZWRpY3RlZF9iYWxhbmNlZCA9PSBkYXRfdGVzdF9iYWxhbmNlZF9yb3NlJGxhYmVsKSkKICB0cHIuZnByLmJhbGFuY2VkIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMocmZfcHJlZGljdGVkX2JhbGFuY2VkKSxkYXRfdGVzdCRsYWJlbCkKICByZl9BVUNfYmFsYW5jZWQgPC0gV2VpZ2h0ZWRBVUModHByLmZwci5iYWxhbmNlZCkKICByZl9hY2N1cmFjeV9pbWJhbGFuY2VkIDwtIG1lYW4ocm91bmQocmZfcHJlZGljdGVkX2ltYmFsYW5jZWQgPT0gZGF0X3Rlc3RfYmFsYW5jZWRfcm9zZSRsYWJlbCkpCiAgdHByLmZwci5pbWJhbGFuY2VkIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMocmZfcHJlZGljdGVkX2ltYmFsYW5jZWQpLGRhdF90ZXN0JGxhYmVsKQogIHJmX0FVQ19pbWJhbGFuY2VkIDwtIFdlaWdodGVkQVVDKHRwci5mcHIuaW1iYWxhbmNlZCkKCiAgY2F0KCJBY2N1cmFjeShiYWxhbmNlZCkgMTUwMCIsIHJmX2FjY3VyYWN5X2JhbGFuY2VkKjEwMCwiJS5cbiIpCiAgY2F0KCJBVUMoYmFsYW5jZWQpIDE1MDAiLCByZl9BVUNfYmFsYW5jZWQsIi5cbiIpCiAgY2F0KCJBY2N1cmFjeShpbWJhbGFuY2VkKSAxNTAwIiwgcmZfYWNjdXJhY3lfaW1iYWxhbmNlZCoxMDAsIiUuXG4iKQogIGNhdCgiQVVDKGltYmFsYW5jZWQpIDE1MDAiLHJmX0FVQ19pbWJhbGFuY2VkLCIuXG4iKQoKICAjMjAwMCB0cmVlcwogIHJmX3ByZWRpY3RlZF9iYWxhbmNlZCA8LSBhcy5udW1lcmljKGFzLnZlY3RvcihyYW5kb21fZm9yZXN0X3Rlc3QocmFuZG9tX2ZvcmVzdF9maXRfMjAwMF90cmVlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UpKSkKICByZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCA8LSBhcy5udW1lcmljKGFzLnZlY3RvcihyYW5kb21fZm9yZXN0X3Rlc3QocmFuZG9tX2ZvcmVzdF9maXRfMjAwMF90cmVlcywgZGF0X3Rlc3QpKSkKCiAgcmZfYWNjdXJhY3lfYmFsYW5jZWQgPC0gbWVhbihyb3VuZChyZl9wcmVkaWN0ZWRfYmFsYW5jZWQgPT0gZGF0X3Rlc3RfYmFsYW5jZWRfcm9zZSRsYWJlbCkpCiAgdHByLmZwci5iYWxhbmNlZCA8LSBXZWlnaHRlZFJPQyhhcy5udW1lcmljKHJmX3ByZWRpY3RlZF9iYWxhbmNlZCksZGF0X3Rlc3QkbGFiZWwpCiAgcmZfQVVDX2JhbGFuY2VkIDwtIFdlaWdodGVkQVVDKHRwci5mcHIuYmFsYW5jZWQpCiAgcmZfYWNjdXJhY3lfaW1iYWxhbmNlZCA8LSBtZWFuKHJvdW5kKHJmX3ByZWRpY3RlZF9pbWJhbGFuY2VkID09IGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UkbGFiZWwpKQogIHRwci5mcHIuaW1iYWxhbmNlZCA8LSBXZWlnaHRlZFJPQyhhcy5udW1lcmljKHJmX3ByZWRpY3RlZF9pbWJhbGFuY2VkKSxkYXRfdGVzdCRsYWJlbCkKICByZl9BVUNfaW1iYWxhbmNlZCA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLmltYmFsYW5jZWQpCgogIGNhdCgiQWNjdXJhY3koYmFsYW5jZWQpIDIwMDAiLCByZl9hY2N1cmFjeV9iYWxhbmNlZCoxMDAsIiUuXG4iKQogIGNhdCgiQVVDKGJhbGFuY2VkKSAyMDAwIiwgcmZfQVVDX2JhbGFuY2VkLCIuXG4iKQogIGNhdCgiQWNjdXJhY3koaW1iYWxhbmNlZCkgMjAwMCIsIHJmX2FjY3VyYWN5X2ltYmFsYW5jZWQqMTAwLCIlLlxuIikKICBjYXQoIkFVQyhpbWJhbGFuY2VkKSAyMDAwIixyZl9BVUNfaW1iYWxhbmNlZCwiLlxuIikKCiAgIyAyNTAwIHRyZWVzCiAgcmZfcHJlZGljdGVkX2JhbGFuY2VkIDwtIGFzLm51bWVyaWMoYXMudmVjdG9yKHJhbmRvbV9mb3Jlc3RfdGVzdChyYW5kb21fZm9yZXN0X2ZpdF8yNTAwX3RyZWVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0X3Rlc3RfYmFsYW5jZWRfcm9zZSkpKQogIHJmX3ByZWRpY3RlZF9pbWJhbGFuY2VkIDwtIGFzLm51bWVyaWMoYXMudmVjdG9yKHJhbmRvbV9mb3Jlc3RfdGVzdChyYW5kb21fZm9yZXN0X2ZpdF8yNTAwX3RyZWVzLCBkYXRfdGVzdCkpKQoKICByZl9hY2N1cmFjeV9iYWxhbmNlZCA8LSBtZWFuKHJvdW5kKHJmX3ByZWRpY3RlZF9iYWxhbmNlZCA9PSBkYXRfdGVzdF9iYWxhbmNlZF9yb3NlJGxhYmVsKSkKICB0cHIuZnByLmJhbGFuY2VkIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMocmZfcHJlZGljdGVkX2JhbGFuY2VkKSxkYXRfdGVzdCRsYWJlbCkKICByZl9BVUNfYmFsYW5jZWQgPC0gV2VpZ2h0ZWRBVUModHByLmZwci5iYWxhbmNlZCkKICByZl9hY2N1cmFjeV9pbWJhbGFuY2VkIDwtIG1lYW4ocm91bmQocmZfcHJlZGljdGVkX2ltYmFsYW5jZWQgPT0gZGF0X3Rlc3RfYmFsYW5jZWRfcm9zZSRsYWJlbCkpCiAgdHByLmZwci5pbWJhbGFuY2VkIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMocmZfcHJlZGljdGVkX2ltYmFsYW5jZWQpLGRhdF90ZXN0JGxhYmVsKQogIHJmX0FVQ19pbWJhbGFuY2VkIDwtIFdlaWdodGVkQVVDKHRwci5mcHIuaW1iYWxhbmNlZCkKCiAgY2F0KCJBY2N1cmFjeShiYWxhbmNlZCkgMjUwMCIsIHJmX2FjY3VyYWN5X2JhbGFuY2VkKjEwMCwiJS5cbiIpCiAgY2F0KCJBVUMoYmFsYW5jZWQpIDI1MDAiLCByZl9BVUNfYmFsYW5jZWQsIi5cbiIpCiAgY2F0KCJBY2N1cmFjeShpbWJhbGFuY2VkKSAyNTAwIiwgcmZfYWNjdXJhY3lfaW1iYWxhbmNlZCoxMDAsIiUuXG4iKQogIGNhdCgiQVVDKGltYmFsYW5jZWQpIDI1MDAiLHJmX0FVQ19pbWJhbGFuY2VkLCIuXG4iKQoKICAjIDEwIG5vZGVzCiAgcmZfcHJlZGljdGVkX2JhbGFuY2VkIDwtIGFzLm51bWVyaWMoYXMudmVjdG9yKHJhbmRvbV9mb3Jlc3RfdGVzdChyYW5kb21fZm9yZXN0X2ZpdF8xMF9ub2RlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UpKSkKICByZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCA8LSBhcy5udW1lcmljKGFzLnZlY3RvcihyYW5kb21fZm9yZXN0X3Rlc3QocmFuZG9tX2ZvcmVzdF9maXRfMTBfbm9kZXMsIGRhdF90ZXN0KSkpCgogIHJmX2FjY3VyYWN5X2JhbGFuY2VkIDwtIG1lYW4ocm91bmQocmZfcHJlZGljdGVkX2JhbGFuY2VkID09IGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UkbGFiZWwpKQogIHRwci5mcHIuYmFsYW5jZWQgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhyZl9wcmVkaWN0ZWRfYmFsYW5jZWQpLGRhdF90ZXN0JGxhYmVsKQogIHJmX0FVQ19iYWxhbmNlZCA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLmJhbGFuY2VkKQogIHJmX2FjY3VyYWN5X2ltYmFsYW5jZWQgPC0gbWVhbihyb3VuZChyZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCA9PSBkYXRfdGVzdF9iYWxhbmNlZF9yb3NlJGxhYmVsKSkKICB0cHIuZnByLmltYmFsYW5jZWQgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhyZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCksZGF0X3Rlc3QkbGFiZWwpCiAgcmZfQVVDX2ltYmFsYW5jZWQgPC0gV2VpZ2h0ZWRBVUModHByLmZwci5pbWJhbGFuY2VkKQoKICBjYXQoIkFjY3VyYWN5KGJhbGFuY2VkKSAxMCIsIHJmX2FjY3VyYWN5X2JhbGFuY2VkKjEwMCwiJS5cbiIpCiAgY2F0KCJBVUMoYmFsYW5jZWQpIDEwIiwgcmZfQVVDX2JhbGFuY2VkLCIuXG4iKQogIGNhdCgiQWNjdXJhY3koaW1iYWxhbmNlZCkgMTAiLCByZl9hY2N1cmFjeV9pbWJhbGFuY2VkKjEwMCwiJS5cbiIpCiAgY2F0KCJBVUMoaW1iYWxhbmNlZCkgMTAiLHJmX0FVQ19pbWJhbGFuY2VkLCIuXG4iKQoKICAjIDE1IG5vZGVzCiAgcmZfcHJlZGljdGVkX2JhbGFuY2VkIDwtIGFzLm51bWVyaWMoYXMudmVjdG9yKHJhbmRvbV9mb3Jlc3RfdGVzdChyYW5kb21fZm9yZXN0X2ZpdF8xNV9ub2RlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UpKSkKICByZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCA8LSBhcy5udW1lcmljKGFzLnZlY3RvcihyYW5kb21fZm9yZXN0X3Rlc3QocmFuZG9tX2ZvcmVzdF9maXRfMTVfbm9kZXMsIGRhdF90ZXN0KSkpCgogIHJmX2FjY3VyYWN5X2JhbGFuY2VkIDwtIG1lYW4ocm91bmQocmZfcHJlZGljdGVkX2JhbGFuY2VkID09IGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UkbGFiZWwpKQogIHRwci5mcHIuYmFsYW5jZWQgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhyZl9wcmVkaWN0ZWRfYmFsYW5jZWQpLGRhdF90ZXN0JGxhYmVsKQogIHJmX0FVQ19iYWxhbmNlZCA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLmJhbGFuY2VkKQogIHJmX2FjY3VyYWN5X2ltYmFsYW5jZWQgPC0gbWVhbihyb3VuZChyZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCA9PSBkYXRfdGVzdF9iYWxhbmNlZF9yb3NlJGxhYmVsKSkKICB0cHIuZnByLmltYmFsYW5jZWQgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhyZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCksZGF0X3Rlc3QkbGFiZWwpCiAgcmZfQVVDX2ltYmFsYW5jZWQgPC0gV2VpZ2h0ZWRBVUModHByLmZwci5pbWJhbGFuY2VkKQoKICBjYXQoIkFjY3VyYWN5KGJhbGFuY2VkKSAxNSIsIHJmX2FjY3VyYWN5X2JhbGFuY2VkKjEwMCwiJS5cbiIpCiAgY2F0KCJBVUMoYmFsYW5jZWQpIDE1IiwgcmZfQVVDX2JhbGFuY2VkLCIuXG4iKQogIGNhdCgiQWNjdXJhY3koaW1iYWxhbmNlZCkgMTUiLCByZl9hY2N1cmFjeV9pbWJhbGFuY2VkKjEwMCwiJS5cbiIpCiAgY2F0KCJBVUMoaW1iYWxhbmNlZCkgMTUiLHJmX0FVQ19pbWJhbGFuY2VkLCIuXG4iKQoKICAjIDIwIG5vZGVzCiAgcmZfcHJlZGljdGVkX2JhbGFuY2VkIDwtIGFzLm51bWVyaWMoYXMudmVjdG9yKHJhbmRvbV9mb3Jlc3RfdGVzdChyYW5kb21fZm9yZXN0X2ZpdF8yMF9ub2RlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UpKSkKICByZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCA8LSBhcy5udW1lcmljKGFzLnZlY3RvcihyYW5kb21fZm9yZXN0X3Rlc3QocmFuZG9tX2ZvcmVzdF9maXRfMjBfbm9kZXMsIGRhdF90ZXN0KSkpCgogIHJmX2FjY3VyYWN5X2JhbGFuY2VkIDwtIG1lYW4ocm91bmQocmZfcHJlZGljdGVkX2JhbGFuY2VkID09IGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UkbGFiZWwpKQogIHRwci5mcHIuYmFsYW5jZWQgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhyZl9wcmVkaWN0ZWRfYmFsYW5jZWQpLGRhdF90ZXN0JGxhYmVsKQogIHJmX0FVQ19iYWxhbmNlZCA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLmJhbGFuY2VkKQogIHJmX2FjY3VyYWN5X2ltYmFsYW5jZWQgPC0gbWVhbihyb3VuZChyZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCA9PSBkYXRfdGVzdF9iYWxhbmNlZF9yb3NlJGxhYmVsKSkKICB0cHIuZnByLmltYmFsYW5jZWQgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhyZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCksZGF0X3Rlc3QkbGFiZWwpCiAgcmZfQVVDX2ltYmFsYW5jZWQgPC0gV2VpZ2h0ZWRBVUModHByLmZwci5pbWJhbGFuY2VkKQoKICBjYXQoIkFjY3VyYWN5KGJhbGFuY2VkKSAyMCIsIHJmX2FjY3VyYWN5X2JhbGFuY2VkKjEwMCwiJS5cbiIpCiAgY2F0KCJBVUMoYmFsYW5jZWQpIDIwIiwgcmZfQVVDX2JhbGFuY2VkLCIuXG4iKQogIGNhdCgiQWNjdXJhY3koaW1iYWxhbmNlZCkgMjAiLCByZl9hY2N1cmFjeV9pbWJhbGFuY2VkKjEwMCwiJS5cbiIpCiAgY2F0KCJBVUMoaW1iYWxhbmNlZCkgMjAiLHJmX0FVQ19pbWJhbGFuY2VkLCIuXG4iKQoKICAjIDI1IG5vZGVzCiAgcmZfcHJlZGljdGVkX2JhbGFuY2VkIDwtIGFzLm51bWVyaWMoYXMudmVjdG9yKHJhbmRvbV9mb3Jlc3RfdGVzdChyYW5kb21fZm9yZXN0X2ZpdF8yNV9ub2RlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UpKSkKICByZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCA8LSBhcy5udW1lcmljKGFzLnZlY3RvcihyYW5kb21fZm9yZXN0X3Rlc3QocmFuZG9tX2ZvcmVzdF9maXRfMjVfbm9kZXMsIGRhdF90ZXN0KSkpCgogIHJmX2FjY3VyYWN5X2JhbGFuY2VkIDwtIG1lYW4ocm91bmQocmZfcHJlZGljdGVkX2JhbGFuY2VkID09IGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UkbGFiZWwpKQogIHRwci5mcHIuYmFsYW5jZWQgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhyZl9wcmVkaWN0ZWRfYmFsYW5jZWQpLGRhdF90ZXN0JGxhYmVsKQogIHJmX0FVQ19iYWxhbmNlZCA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLmJhbGFuY2VkKQogIHJmX2FjY3VyYWN5X2ltYmFsYW5jZWQgPC0gbWVhbihyb3VuZChyZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCA9PSBkYXRfdGVzdF9iYWxhbmNlZF9yb3NlJGxhYmVsKSkKICB0cHIuZnByLmltYmFsYW5jZWQgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhyZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCksZGF0X3Rlc3QkbGFiZWwpCiAgcmZfQVVDX2ltYmFsYW5jZWQgPC0gV2VpZ2h0ZWRBVUModHByLmZwci5pbWJhbGFuY2VkKQoKICBjYXQoIkFjY3VyYWN5KGJhbGFuY2VkKSAyNSIsIHJmX2FjY3VyYWN5X2JhbGFuY2VkKjEwMCwiJS5cbiIpCiAgY2F0KCJBVUMoYmFsYW5jZWQpIDI1IiwgcmZfQVVDX2JhbGFuY2VkLCIuXG4iKQogIGNhdCgiQWNjdXJhY3koaW1iYWxhbmNlZCkgMjUiLCByZl9hY2N1cmFjeV9pbWJhbGFuY2VkKjEwMCwiJS5cbiIpCiAgY2F0KCJBVUMoaW1iYWxhbmNlZCkgMjUiLHJmX0FVQ19pbWJhbGFuY2VkLCIuXG4iKQoKICAjIDMwIG5vZGVzCiAgcmZfcHJlZGljdGVkX2JhbGFuY2VkIDwtIGFzLm51bWVyaWMoYXMudmVjdG9yKHJhbmRvbV9mb3Jlc3RfdGVzdChyYW5kb21fZm9yZXN0X2ZpdF8zMF9ub2RlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UpKSkKICByZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCA8LSBhcy5udW1lcmljKGFzLnZlY3RvcihyYW5kb21fZm9yZXN0X3Rlc3QocmFuZG9tX2ZvcmVzdF9maXRfMzBfbm9kZXMsIGRhdF90ZXN0KSkpCgogIHJmX2FjY3VyYWN5X2JhbGFuY2VkIDwtIG1lYW4ocm91bmQocmZfcHJlZGljdGVkX2JhbGFuY2VkID09IGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UkbGFiZWwpKQogIHRwci5mcHIuYmFsYW5jZWQgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhyZl9wcmVkaWN0ZWRfYmFsYW5jZWQpLGRhdF90ZXN0JGxhYmVsKQogIHJmX0FVQ19iYWxhbmNlZCA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLmJhbGFuY2VkKQogIHJmX2FjY3VyYWN5X2ltYmFsYW5jZWQgPC0gbWVhbihyb3VuZChyZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCA9PSBkYXRfdGVzdF9iYWxhbmNlZF9yb3NlJGxhYmVsKSkKICB0cHIuZnByLmltYmFsYW5jZWQgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhyZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCksZGF0X3Rlc3QkbGFiZWwpCiAgcmZfQVVDX2ltYmFsYW5jZWQgPC0gV2VpZ2h0ZWRBVUModHByLmZwci5pbWJhbGFuY2VkKQoKICBjYXQoIkFjY3VyYWN5KGJhbGFuY2VkKSAzMCIsIHJmX2FjY3VyYWN5X2JhbGFuY2VkKjEwMCwiJS5cbiIpCiAgY2F0KCJBVUMoYmFsYW5jZWQpIDMwIiwgcmZfQVVDX2JhbGFuY2VkLCIuXG4iKQogIGNhdCgiQWNjdXJhY3koaW1iYWxhbmNlZCkgMzAiLCByZl9hY2N1cmFjeV9pbWJhbGFuY2VkKjEwMCwiJS5cbiIpCiAgY2F0KCJBVUMoaW1iYWxhbmNlZCkgMzAiLHJmX0FVQ19pbWJhbGFuY2VkLCIuXG4iKQp9CmBgYAoKKioxNSBOb2RlcyBhbmQgMjUwMCB0cmVlcyBpcyB0aGUgYmVzdC4qKgoKKiBUcmFpbiBSYW5kb20gRm9yZXN0IHdpdGggVHVuZWQgUGFyYW1ldGVycwoKYGBge3J9CmlmKHJ1bi5yZil7CiAgaWYodHJhaW4ucmFuZG9tLmZvcmVzdCl7CiAgICB0aW1lLnJmLnRyYWluLmZpbmFsLmJhbGFuY2VkIDwtIHN5c3RlbS50aW1lKHJhbmRvbV9mb3Jlc3RfZml0X2ZpbmFsX2JhbGFuY2VkIDwtCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmFuZG9tX2ZvcmVzdF90cmFpbihkYXRfdHJhaW5fYmFsYW5jZWRfcm9zZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gMzA4LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJlZV9udW1iZXIgPSAyMDAwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vZGVfc2l6ZSA9IDE1KSkKICAgIHNhdmUocmFuZG9tX2ZvcmVzdF9maXRfZmluYWxfYmFsYW5jZWQsIGZpbGUgPSAiLi4vb3V0cHV0L3JmX3RyYWluX2ZpbmFsX2JhbGFuY2VkX29sZF9mZWF0dXJlLlJEYXRhIikKICAgIHNhdmUodGltZS5yZi50cmFpbi5maW5hbC5iYWxhbmNlZCwgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fZmluYWxfdGltZV9iYWxhbmNlZF9vbGRfZmVhdHVyZS5SRGF0YSIpCiAgICB0aW1lLnJmLnRyYWluLmZpbmFsLmltYmFsYW5jZWQgPC0gc3lzdGVtLnRpbWUocmFuZG9tX2ZvcmVzdF9maXRfZmluYWxfaW1iYWxhbmNlZCA8LQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhbmRvbV9mb3Jlc3RfdHJhaW4oZGF0X3RyYWluLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSAzMDgsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJlZV9udW1iZXIgPSAxMDAwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm9kZV9zaXplID0gMTUpKQogICAgc2F2ZSh0aW1lLnJmLnRyYWluLmZpbmFsLmltYmFsYW5jZWQsIGZpbGUgPSAiLi4vb3V0cHV0L3JmX3RyYWluX2ZpbmFsX3RpbWVfaW1iYWxhbmNlZF9vbGRfZmVhdHVyZS5SRGF0YSIpCiAgICBzYXZlKHJhbmRvbV9mb3Jlc3RfZml0X2ZpbmFsX2ltYmFsYW5jZWQsIGZpbGUgPSAiLi4vb3V0cHV0L3JmX3RyYWluX2ZpbmFsX2ltYmFsYW5jZWRfb2xkX2ZlYXR1cmUuUkRhdGEiKQogIH0gZWxzZSB7CiAgICBsb2FkKCIuLi9vdXRwdXQvcmZfdHJhaW5fZmluYWxfYmFsYW5jZWRfb2xkX2ZlYXR1cmUuUkRhdGEiKQogICAgbG9hZCgiLi4vb3V0cHV0L3JmX3RyYWluX2ZpbmFsX3RpbWVfYmFsYW5jZWRfb2xkX2ZlYXR1cmUuUkRhdGEiKQogICAgbG9hZCgiLi4vb3V0cHV0L3JmX3RyYWluX2ZpbmFsX3RpbWVfaW1iYWxhbmNlZF9vbGRfZmVhdHVyZS5SRGF0YSIpCiAgICBsb2FkKCIuLi9vdXRwdXQvcmZfdHJhaW5fZmluYWxfaW1iYWxhbmNlZF9vbGRfZmVhdHVyZS5SRGF0YSIpCiAgfQp9CmBgYAoKKiBUZXN0IGFuZCBFdmFsdWF0ZSBSYW5kb20gRm9yZXN0IHdpdGggVHVuZWQgUGFyYW1ldGVycwoKYGBge3J9CmlmKHJ1bi5yZil7CiAgIyBCYWxhbmNlZDoKICBpZih0ZXN0LnJhbmRvbS5mb3Jlc3QpewogIHRpbWUucmYudGVzdC5maW5hbC5iYWxhbmNlZCA8LSBzeXN0ZW0udGltZSgKICAgIHJmX3ByZWRpY3RlZF9iYWxhbmNlZCA8LSBhcy5udW1lcmljKGFzLnZlY3RvcihyYW5kb21fZm9yZXN0X3Rlc3QocmFuZG9tX2ZvcmVzdF9maXRfZmluYWxfYmFsYW5jZWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdF90ZXN0X2JhbGFuY2VkX3Jvc2UpKSkpCiAgcmZfYWNjdXJhY3lfYmFsYW5jZWQgPC0gbWVhbihyb3VuZChyZl9wcmVkaWN0ZWRfYmFsYW5jZWQgPT0gZGF0X3Rlc3RfYmFsYW5jZWRfcm9zZSRsYWJlbCkpCiAgdHByLmZwci5iYWxhbmNlZCA8LSBXZWlnaHRlZFJPQyhhcy5udW1lcmljKHJmX3ByZWRpY3RlZF9iYWxhbmNlZCksZGF0X3Rlc3RfYmFsYW5jZWRfcm9zZSRsYWJlbCkKICByZl9BVUNfYmFsYW5jZWQgPC0gV2VpZ2h0ZWRBVUModHByLmZwci5iYWxhbmNlZCkKCiAgY2F0KCJBVUMoYmFsYW5jZWQpOiAiLCByZl9BVUNfYmFsYW5jZWQsIi5cbiIpCiAgY2F0KCJBY2N1cmFjeShiYWxhbmNlZCkiLCByZl9hY2N1cmFjeV9iYWxhbmNlZCoxMDAsIiUuXG4iKQogIGNhdCgiVHJhaW5pbmcgdGltZTogIiwgdGltZS5yZi50cmFpbi5maW5hbC5iYWxhbmNlZFsxXSwgInMuXG4iKQogIGNhdCgiVGVzdGluZyB0aW1lOiAiLCB0aW1lLnJmLnRlc3QuZmluYWwuYmFsYW5jZWRbMV0sICJzLlxuIikKCiAgIyBJbWJhbGFuY2VkOgogIHRpbWUucmYudGVzdC5maW5hbC5pbWJhbGFuY2VkIDwtIHN5c3RlbS50aW1lKAogICAgcmZfcHJlZGljdGVkX2ltYmFsYW5jZWQgPC1hcy5udW1lcmljKGFzLnZlY3RvcihyYW5kb21fZm9yZXN0X3Rlc3QocmFuZG9tX2ZvcmVzdF9maXRfZmluYWxfaW1iYWxhbmNlZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdF90ZXN0KSkpKQogIHJmX2FjY3VyYWN5X2ltYmFsYW5jZWQgPC0gbWVhbihyb3VuZChyZl9wcmVkaWN0ZWRfaW1iYWxhbmNlZCA9PSBkYXRfdGVzdCRsYWJlbCkpCiAgdHByLmZwci5pbWJhbGFuY2VkIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMocmZfcHJlZGljdGVkX2ltYmFsYW5jZWQpLGRhdF90ZXN0JGxhYmVsKQogIHJmX0FVQ19pbWJhbGFuY2VkIDwtIFdlaWdodGVkQVVDKHRwci5mcHIuaW1iYWxhbmNlZCkKCiAgY2F0KCJBVUMoaW1iYWxhbmNlZCk6ICIsIHJmX0FVQ19pbWJhbGFuY2VkLCIuXG4iKQogIGNhdCgiQWNjdXJhY3koaW1iYWxhbmNlZCkiLCByZl9hY2N1cmFjeV9pbWJhbGFuY2VkKjEwMCwiJS5cbiIpCiAgY2F0KCJUcmFpbmluZyB0aW1lOiAiLCB0aW1lLnJmLnRyYWluLmZpbmFsLmltYmFsYW5jZWRbMV0sICJzLlxuIikKICBjYXQoIlRlc3RpbmcgdGltZTogIiwgdGltZS5yZi50ZXN0LmZpbmFsLmltYmFsYW5jZWRbMV0sICJzLlxuIikKICB9Cn0KYGBgCgotLS0tLS0tLVRISVMgSVMgVE8gU0VQQVJBVEUgRUFDSCBNT0RFTC4gVEhJUyBJUyBUTyBTRVBBUkFURSBFQUNIIE1PREVMLiBUSElTIElTIFRPIFNFUEFSQVRFIEVBQ0ggTU9ERUwuLS0tLS0tLS0tLQoKCgoKIyBBZHZhbmNlZCBNb2RlbCAzOiBTVk0gTW9kZWwKCiogQmFsYW5jZSB0aGUgVHJhaW5pbmcgU2V0CgpgYGB7cn0KaWYocnVuLnN2bSl7CiAgdG1fc3ZtX3JlYmFsYW5jZWRfdHJhaW4gPC0gTkEKICBpZihzYW1wbGUucmV3ZWlnaHQpewogICAgdG1fc3ZtX3JlYmFsYW5jZWRfdHJhaW4gPC0gc3lzdGVtLnRpbWUoc3ZtX3RyYWluaW5nX2RhdGEgPC0gUk9TRShsYWJlbCB+IC4sIGRhdGEgPSBkYXRfdHJhaW4pJGRhdGEpCiAgICBzYXZlKHN2bV90cmFpbmluZ19kYXRhLCBmaWxlPSIuLi9vdXRwdXQvc3ZtX3RyYWluaW5nX2RhdGEuUkRhdGEiKQogICAgc2F2ZSh0bV9zdm1fcmViYWxhbmNlZF90cmFpbiwgZmlsZT0iLi4vb3V0cHV0L3RtX3N2bV9yZWJhbGFuY2VkX3RyYWluLlJEYXRhIikKICB9IGVsc2UgewogICAgc3ZtX3RyYWluaW5nX2RhdGEgPC0gZGF0X3RyYWluCiAgICB0bV9zdm1fcmViYWxhbmNlZF90cmFpbiA8LSB0bV9mZWF0dXJlX3RyYWluCiAgfQp9ICNlbHNlIHsKICAjbG9hZChmaWxlPSIuLi9vdXRwdXQvdG1fc3ZtX3JlYmFsYW5jZWRfdHJhaW4uUkRhdGEiKQojfQpgYGAKCiogTW9kZWwgU2VsZWN0aW9uCgpgYGB7cn0KaWYocnVuLnN2bSl7CiAgdG1fc3ZtX2xpbmVhcl9tb2QgPC0gTkEKICB0bV9zdm1fcmFkaWFsX21vZCA8LSBOQQogIAogIGlmKG1vZGVsLnNlbGVjdGlvbil7CiAgICBzdm1fbW9kZWxfYXVjIDwtIHJlcChOQSwgMikKICAgIAogICAgIyMjIGxpbmVhciBrZXJuZWwKICAgIGlmKHJ1bi5jdil7CiAgICAgICNiZXN0LmxpbmVhci5jb3N0IDwtIHN2bV9saW5lYXJfY29zdF90dW5lKHN2bV90cmFpbmluZ19kYXRhKQogICAgICAjY2F0KCJUaGUgYmVzdCBjb3N0IGZvciBzdm0gbW9kZWwgd2l0aCBsaW5lYXIga2VybmVsIGlzOiAiLCBiZXN0LmxpbmVhci5jb3N0JGJlc3QucGFyYW1ldGVycyRjb3N0KQogICAgICB0bV9zdm1fbGluZWFyX21vZCA8LSBzeXN0ZW0udGltZShzdm1fbGluZWFyX21vZCA8LSBzdm1fbGluZWFyX3RyYWluKHN2bV90cmFpbmluZ19kYXRhLCAwLjAxLCBLKSkKICAgICAgc2F2ZShzdm1fbGluZWFyX21vZCwgZmlsZT0iLi4vb3V0cHV0L3N2bV9saW5lYXJfbW9kLlJEYXRhIikKICAgICAgc2F2ZSh0bV9zdm1fbGluZWFyX21vZCwgZmlsZT0iLi4vb3V0cHV0L3RtX3N2bV9saW5lYXJfbW9kLlJEYXRhIikKICAgIH0gZWxzZSB7CiAgICAgIGxvYWQoZmlsZT0iLi4vb3V0cHV0L3N2bV9saW5lYXJfbW9kLlJEYXRhIikKICAgICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvdG1fc3ZtX2xpbmVhcl9tb2QuUkRhdGEiKQogICAgfQogICAgc3ZtX2xpbmVhcl9wcmVkIDwtIHN2bV90ZXN0KHN2bV9saW5lYXJfbW9kLCBzdm1fdHJhaW5pbmdfZGF0YSwgVFJVRSkKICAgICNtZWFuKHJvdW5kKHN2bV9saW5lYXJfcHJlZCA9PSBzdm1fdHJhaW5pbmdfZGF0YSRsYWJlbCkpCiAgICBzdm1fbGluZWFyX2FjY3UgPC0gbWVhbihyb3VuZChzdm1fbGluZWFyX3ByZWQgPT0gc3ZtX3RyYWluaW5nX2RhdGEkbGFiZWwpKQogICAgdHByLmZwcl9saW5lYXIgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhzdm1fbGluZWFyX3ByZWQpLCBzdm1fdHJhaW5pbmdfZGF0YSRsYWJlbCkKICAgIHN2bV9tb2RlbF9hdWNbMV0gPC0gV2VpZ2h0ZWRBVUModHByLmZwcl9saW5lYXIpCiAgICAKICAgIAogICAgIyMjIHJhZGlhbCBiYXNpcyBrZXJuZWwKICAgIGlmKHJ1bi5jdil7CiAgICAgICNiZXN0LnJhZGlhbC5jb3N0IDwtIHN2bV9yYWRpYWxfY29zdF90dW5lKHN2bV90cmFpbmluZ19kYXRhKQogICAgICAjcmFkaWFsX2Nvc3QgPSBiZXN0LnJhZGlhbC5jb3N0JGJlc3QucGFyYW1ldGVycyRjb3N0CiAgICAgICNyYWRpYWxfZ2FtbWEgPSBiZXN0LnJhZGlhbC5jb3N0JGJlc3QucGFyYW1ldGVycyRnYW1tYQogICAgICAKICAgICAgdG1fc3ZtX3JhZGlhbF9tb2QgPCBzeXN0ZW0udGltZShzdm1fcmFkaWFsX21vZCA8LSBzdm1fcmFkaWFsX3RyYWluKHN2bV90cmFpbmluZ19kYXRhLCAxLCBLKSkKICAgICAgc2F2ZShzdm1fcmFkaWFsX21vZCwgZmlsZT0iLi4vb3V0cHV0L3N2bV9yYWRpYWxfbW9kLlJEYXRhIikKICAgICAgc2F2ZSh0bV9zdm1fcmFkaWFsX21vZCwgZmlsZT0iLi4vb3V0cHV0L3RtX3N2bV9yYWRpYWxfbW9kLlJEYXRhIikKICAgIH0gZWxzZSB7IAogICAgICBsb2FkKGZpbGU9Ii4uL291dHB1dC9zdm1fcmFkaWFsX21vZC5SRGF0YSIpCiAgICAgIGxvYWQoZmlsZT0iLi4vb3V0cHV0L3RtX3N2bV9yYWRpYWxfbW9kLlJEYXRhIikKICAgIH0KICAgIHN2bV9yYWRpYWxfcHJlZCA8LSBzdm1fdGVzdChzdm1fcmFkaWFsX21vZCwgc3ZtX3RyYWluaW5nX2RhdGEsIFRSVUUpCiAgICAjIGV2YWx1YXRlIHBlcmZvcm1hbmNlCiAgICBzdm1fcmFkaWFsX2FjY3UgPC0gbWVhbihyb3VuZChzdm1fcmFkaWFsX3ByZWQgPT0gc3ZtX3RyYWluaW5nX2RhdGEkbGFiZWwpKQogICAgdHByLmZwcl9kZWZhdWx0IDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMoc3ZtX3JhZGlhbF9wcmVkKSwgc3ZtX3RyYWluaW5nX2RhdGEkbGFiZWwpCiAgICBzdm1fbW9kZWxfYXVjWzJdIDwtIFdlaWdodGVkQVVDKHRwci5mcHJfZGVmYXVsdCkKICB9IGVsc2UgewogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvc3ZtX2xpbmVhcl9tb2QuUkRhdGEiKQogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvdG1fc3ZtX2xpbmVhcl9tb2QuUkRhdGEiKQogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvc3ZtX3JhZGlhbF9tb2QuUkRhdGEiKQogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvdG1fc3ZtX3JhZGlhbF9tb2QuUkRhdGEiKQogIH0KICAKICAKICAjIyMgRXZhbHVhdGlvbiBvbiBUZXN0aW5nIERhdGEKICB0bV9zdm1fcmViYWxhbmNlZF90ZXN0IDwtIE5BCiAgaWYoc2FtcGxlLnJld2VpZ2h0KXsKICAgIHRtX3N2bV9yZWJhbGFuY2VkX3Rlc3QgPC0gc3lzdGVtLnRpbWUoc3ZtX3Rlc3RpbmdfZGF0YSA8LSBST1NFKGxhYmVsIH4gLiwgZGF0YSA9IGRhdF90ZXN0KSRkYXRhKQogICAgc2F2ZShzdm1fdGVzdGluZ19kYXRhLCBmaWxlPSIuLi9vdXRwdXQvc3ZtX3Rlc3RpbmdfZGF0YS5SRGF0YSIpCiAgICBzYXZlKHRtX3N2bV9yZWJhbGFuY2VkX3Rlc3QsIGZpbGU9Ii4uL291dHB1dC90bV9zdm1fcmViYWxhbmNlZF90ZXN0LlJEYXRhIikKICB9IGVsc2UgewogICAgc3ZtX3Rlc3RpbmdfZGF0YSA8LSBkYXRfdGVzdAogICAgdG1fc3ZtX3JlYmFsYW5jZWRfdGVzdCA8LSB0bV9mZWF0dXJlX3Rlc3QKICB9CiAgCiAgaWYocnVuLnN2bS50ZXN0KXsKICAgIHN2bV9hdWMgPC0gcmVwKE5BLCAyKQogICAgc3ZtX2FjY3UgPC0gcmVwKE5BLCAyKQogICAgIyMgbGluZWFyCiAgICB0bV9zdm1fbGluZWFyX3Rlc3QgPC0gc3lzdGVtLnRpbWUoc3ZtX2xpbmVhcl9wcmVkIDwtIHN2bV90ZXN0KHN2bV9saW5lYXJfbW9kLCBzdm1fdGVzdGluZ19kYXRhKSkKICAgIHN2bV9hY2N1WzFdID0gbWVhbihyb3VuZChzdm1fbGluZWFyX3ByZWQgPT0gc3ZtX3Rlc3RpbmdfZGF0YSRsYWJlbCkpCiAgICB0cHIuZnByLmxpbmVhciA8LSBXZWlnaHRlZFJPQyhhcy5udW1lcmljKHN2bV9saW5lYXJfcHJlZCksIHN2bV90ZXN0aW5nX2RhdGEkbGFiZWwpCiAgICBzdm1fYXVjWzFdID0gV2VpZ2h0ZWRBVUModHByLmZwci5saW5lYXIpCiAgICAjIyByYmYKICAgIHRtX3N2bV9yYmZfdGVzdCA8LSBzeXN0ZW0udGltZShzdm1fcmJmX3ByZWQgPC0gc3ZtX3Rlc3Qoc3ZtX3JhZGlhbF9tb2QsIHN2bV90ZXN0aW5nX2RhdGEpKQogICAgc3ZtX2FjY3VbMl0gPSBtZWFuKHJvdW5kKHN2bV9yYmZfcHJlZCA9PSBzdm1fdGVzdGluZ19kYXRhJGxhYmVsKSkKICAgIHRwci5mcHIucmJmIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMoc3ZtX2xpbmVhcl9wcmVkKSwgc3ZtX3Rlc3RpbmdfZGF0YSRsYWJlbCkKICAgIHN2bV9hdWNbMl0gPSBXZWlnaHRlZEFVQyh0cHIuZnByLnJiZikKICAgIAogICAgc2F2ZSh0bV9zdm1fcmFkaWFsX21vZCwgZmlsZT0iLi4vb3V0cHV0L3RtX3N2bV9saW5lYXJfdGVzdC5SRGF0YSIpCiAgICAKICAgICMjIHBlcmZvcm1hbmNlCiAgICBzdm1fYXVjCiAgICBzdm1fYWNjdQoKICAgIGNhdCgiVGhlIGFjY3VyYWN5IG9mIHN2bSBtb2RlbCBpcyIsIHN2bV9hY2N1WzJdKjEwMCwgIiUuXG4iKQogICAgY2F0KCJUaGUgQVVDIG9mIHN2bSBtb2RlbCBpcyIsIHN2bV9hdWNbMl0sICIuXG4iKQogIH0gZWxzZSB7CiAgICBsb2FkKGZpbGU9Ii4uL291dHB1dC90bV9zdm1fcmViYWxhbmNlZF90ZXN0LlJEYXRhIikKICB9Cn0jIGVsc2UgewogICNsb2FkKGZpbGU9Ii4uL291dHB1dC9zdm1fcmFkaWFsX21vZC5SRGF0YSIpCiAgI2xvYWQoZmlsZT0iLi4vb3V0cHV0L3RtX3N2bV9yYWRpYWxfbW9kLlJEYXRhIikKI30KYGBgCgoqIFN1bW1hcml6ZSBSdW5uaW5nIFRpbWUKCmBgYHtyfQppZihydW4uc3ZtKXsKICAjY2F0KCJUaW1lIGZvciByZWJhbGFuY2luZyB0cmFpbmluZyBkYXRhID0iLCB0bV9zdm1fcmViYWxhbmNlZF90cmFpblsxXSwgInMgXG4iKQogICNjYXQoIlRpbWUgZm9yIHJlYmFsYW5jaW5nIHRlc3RpbmcgZGF0YSA9IiwgdG1fc3ZtX3JlYmFsYW5jZWRfdGVzdFsxXSwgInMgXG4iKQogICNjYXQoIlRpbWUgZm9yIHRyYWluaW5nIHN2bSBtb2RlbCA9IiwgdG1fc3ZtX3JhZGlhbF9tb2RbMV0sICJzIFxuIikKICBjYXQoIlRpbWUgZm9yIHRlc3Rpbmcgc3ZtIG1vZGVsPSIsIHRtX3N2bV9yYmZfdGVzdFsxXSwgInMgXG4iKQp9CmBgYAoKLS0tLS0tLS1USElTIElTIFRPIFNFUEFSQVRFIEVBQ0ggTU9ERUwuIFRISVMgSVMgVE8gU0VQQVJBVEUgRUFDSCBNT0RFTC4gVEhJUyBJUyBUTyBTRVBBUkFURSBFQUNIIE1PREVMLi0tLS0tLS0tLS0KCgoKCiMgQWR2YW5jZWQgTW9kZWwgNDogUmlkZ2UgTW9kZWwKCiogQXBwbHkgQ29uc3RydWN0ZWQgUmlkZ2UgTW9kZWwgdG8gdGhlIFRyYWluaW5nIFNldAoKYGBge3J9CmlmKHJ1bi5yaWRnZSl7CiAgdG1fcmlkZ2VfdHJhaW4gPC0gTkEKICBpZiAodHJhaW4ucmlkZ2UpewogICAgZGF0X3RyYWluX3JlYmFsYW5jZWQgPC0gUk9TRShsYWJlbCB+IC4sIGRhdGEgPSBkYXRfdHJhaW4sIHNlZWQ9MjAyMCkkZGF0YQogICAgdG1fcmlkZ2VfdHJhaW4gPC0gc3lzdGVtLnRpbWUocmlkZ2VfY3ZfbW9kZWw8LXJpZGdlX3RyYWluKHRyYWluX2RhdGE9ZGF0X3RyYWluX3JlYmFsYW5jZWQsIGFscGhhPWFscGhhLCBLPUssIGxhbWJkYT1sYW1iZGEpKQogICAgc2F2ZShyaWRnZV9jdl9tb2RlbCwgZmlsZT0iLi4vb3V0cHV0L3JpZGdlX2N2X21vZGVsLlJEYXRhIikKICAgIHNhdmUodG1fcmlkZ2VfdHJhaW4sIGZpbGU9Ii4uL291dHB1dC9yaWRnZV90cmFpbl90aW1lLlJEYXRhIikKICB9IGVsc2UgewogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvcmlkZ2VfY3ZfbW9kZWwuUkRhdGEiKQogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvcmlkZ2VfdHJhaW5fdGltZS5SRGF0YSIpCiAgfQp9CmBgYAoKKiBVc2UgQ3Jvc3MtVmFsaWRhdGlvbiB0byBDaG9vc2UgdGhlIE9wdGltYWwgTGFtYmRhIHdpdGggU21hbGxlc3QgTVNFCgpgYGB7cn0KaWYocnVuLnJpZGdlKXsKICBpZiAocnVuLmN2KXsKICAgIHNldC5zZWVkKDIwMjApCiAgICBkYXRfdHJhaW5fcmViYWxhbmNlZCA8LSBST1NFKGxhYmVsIH4gLiwgZGF0YSA9IGRhdF90cmFpbiwgc2VlZD0yMDIwKSRkYXRhCiAgICBmZWF0dXJlX3RyYWluID0gYXMubWF0cml4KGRhdF90cmFpbl9yZWJhbGFuY2VkWywtZGltKGRhdF90cmFpbl9yZWJhbGFuY2VkKVsyXV0pCiAgICBsYWJlbF90cmFpbiA9IGFzLmludGVnZXIoZGF0X3RyYWluX3JlYmFsYW5jZWQkbGFiZWwpCiAgICByaWRnZV9tb2RlbCA9IGN2LmdsbW5ldCh4PWZlYXR1cmVfdHJhaW4sIHk9bGFiZWxfdHJhaW4sIGFscGhhPWFscGhhLCBuZm9sZHM9SywgbGFtYmRhPWxhbWJkYSkKICAgIG9wdF9sYW1iZGEgPSByaWRnZV9tb2RlbCRsYW1iZGEubWluCiAgICBzYXZlKG9wdF9sYW1iZGEsIGZpbGU9Ii4uL291dHB1dC9yaWRnZV9vcHRpbWFsX2xhbWJkYS5SRGF0YSIpCiAgfSBlbHNlIHsKICAgIGxvYWQoZmlsZT0iLi4vb3V0cHV0L3JpZGdlX29wdGltYWxfbGFtYmRhLlJEYXRhIikKICB9Cn0KYGBgCgoqIFByZWRpY3Qgb24gVGVzdGluZyBTZXQgd2l0aCB0aGUgT3B0aW1hbCBMYW1iZGEKCmBgYHtyfQppZihydW4ucmlkZ2UpewogIHRtX3JpZGdlX3Rlc3QgPSBOQQogIGlmKHJ1bi50ZXN0KXsKICAgIGxvYWQoIi4uL291dHB1dC9yaWRnZV9jdl9tb2RlbC5SRGF0YSIpCiAgICBkYXRfdGVzdF9yZWJhbGFuY2VkIDwtIFJPU0UobGFiZWwgfiAuLCBkYXRhID0gZGF0X3Rlc3QsIHNlZWQ9MjAyMCkkZGF0YQogICAgZmVhdHVyZV90ZXN0IDwtIGFzLm1hdHJpeChkYXRfdGVzdF9yZWJhbGFuY2VkWywgLWRpbShkYXRfdGVzdF9yZWJhbGFuY2VkKVsyXV0pCiAgICB0bV9yaWRnZV90ZXN0IDwtIHN5c3RlbS50aW1lKGxhYmVsX3ByZWQ8LWFzLmludGVnZXIocmlkZ2VfdGVzdChtb2RlbD1yaWRnZV9jdl9tb2RlbCwgZmVhdHVyZXM9ZmVhdHVyZV90ZXN0LCBwcmVkLnR5cGUgPSAnY2xhc3MnKSkpCiAgICBzYXZlKHRtX3JpZGdlX3Rlc3QsIGZpbGU9Ii4uL291dHB1dC9yaWRnZV90ZXN0X3RpbWUuUkRhdGEiKQogIH0gZWxzZXsKICAgIGxvYWQoZmlsZT0iLi4vb3V0cHV0L3JpZGdlX3Rlc3RfdGltZS5SRGF0YSIpCiAgfQp9CmBgYAoKKiBTdW1tYXJpemUgUnVubmluZyBUaW1lCgpgYGB7cn0KaWYocnVuLnJpZGdlKXsKICBjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0cmFpbmluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3RyYWluWzFdLCAicyBcbiIpCiAgY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdGVzdGluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3Rlc3RbMV0sICJzIFxuIikKICBjYXQoIlRpbWUgZm9yIHRyYWluaW5nIHJpZGdlIG1vZGVsPSIsIHRtX3JpZGdlX3RyYWluWzFdLCAicyBcbiIpIAogIGNhdCgiVGltZSBmb3IgdGVzdGluZyByaWRnZSBtb2RlbD0iLCB0bV9yaWRnZV90ZXN0WzFdLCAicyBcbiIpCn0KYGBgCgoqIEV2YWx1YXRpb24gb24gSW5kZXBlbmRlbnQgVGVzdGluZyBEYXRhIAoKYGBge3J9CmlmKHJ1bi5yaWRnZSl7CiAgbG9hZCgiLi4vb3V0cHV0L3JpZGdlX2N2X21vZGVsLlJEYXRhIikKICBkYXRfdGVzdF9yZWJhbGFuY2VkIDwtIFJPU0UobGFiZWwgfiAuLCBkYXRhID0gZGF0X3Rlc3QsIHNlZWQ9MjAyMCkkZGF0YQogIGZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RfcmViYWxhbmNlZFssIC1kaW0oZGF0X3Rlc3RfcmViYWxhbmNlZClbMl1dKQogIGxhYmVsX3ByZWQgPSBhcy5pbnRlZ2VyKHByZWRpY3QocmlkZ2VfY3ZfbW9kZWwsIHM9b3B0X2xhbWJkYSwgbmV3eD1mZWF0dXJlX3Rlc3QsIHR5cGU9J2NsYXNzJykpCiAgbGFiZWxfdGVzdCA9IGFzLmludGVnZXIoZGF0X3Rlc3RfcmViYWxhbmNlZCRsYWJlbCkKICByaWRnZV9hY2N1cmFjeSA9IG1lYW4ocm91bmQobGFiZWxfdGVzdD09IGxhYmVsX3ByZWQpKQogIGNhdCgiVGhlIGFjY3VyYWN5IG9mIHRoZSByaWRnZSBtb2RlbCBpcyIsIHJpZGdlX2FjY3VyYWN5KjEwMCwgIiUuXG4iKQogIHJpZGdlX0FVQyA9IGF1Yyhyb2MobGFiZWxfcHJlZCxsYWJlbF90ZXN0KSkKICBjYXQoIlRoZSBBVUMgb2YgdGhlIHJpZGdlIG1vZGVsIGlzIiwgcmlkZ2VfQVVDLCAiLlxuIikKfQpgYGAKCi0tLS0tLS0tVEhJUyBJUyBUTyBTRVBBUkFURSBFQUNIIE1PREVMLiBUSElTIElTIFRPIFNFUEFSQVRFIEVBQ0ggTU9ERUwuIFRISVMgSVMgVE8gU0VQQVJBVEUgRUFDSCBNT0RFTC4tLS0tLS0tLS0tCgoKCgojIEFkdmFuY2VkIE1vZGVsIDU6IFBDQSArIExEQQoKKiBSZWJhbGFuY2UgVHJhaW5pbmcgU2V0CgpgYGB7cn0KaWYocnVuLnBjYV9sZGEpewogIGlmKHNhbXBsZS5yZXdlaWdodCl7CiAgICBiYWxhbmNlZF90cmFpbl9kYXRhIDwtIFJPU0UobGFiZWx+LixkYXRhID0gZGF0X3RyYWluKSRkYXRhCiAgICBzYXZlKGJhbGFuY2VkX3RyYWluX2RhdGEsIGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX2JhbGFuY2VkX3RyYWluLlJEYXRhIikKICB9IGVsc2UgewogICAgbG9hZChiYWxhbmNlZF90cmFpbl9kYXRhLCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90cmFpbi5SRGF0YSIpCiAgfQp9CmBgYAoKKiBQZXJmb3JtIFBDQSBmb3IgRGltZW5zaW9uIFJlZHVjdGlvbgoKKipTaW5jZSB0aGVyZSBhcmUgb3ZlciA2MDAwIGZlYXR1cmVzLCB3ZSBpbXBsZW1lbnQgdGhlIFBDQSBtZXRob2QgdG8gcmVkdWNlIGRpbWVuc2lvbiBhY2NvcmRpbmcgdG8gdGhlIGNvdmFyaWFuY2UgbWF0cml4LiBXZSBvbmx5IHJldGFpbiBQQ3Mgd2l0aCBsYXJnZSB2YXJpYW5jZS4qKgoKYGBge3IgcGNhIGxkYX0KaWYocnVuLnBjYV9sZGEpewogIGlmKHJ1bi5wY2FfbGFkLnRlc3QpewogICAgaWYoc2FtcGxlLnJld2VpZ2h0KXsKICAgICAgYmFsYW5jZWRfdGVzdF9kYXRhIDwtIFJPU0UobGFiZWx+LixkYXRhID0gZGF0X3Rlc3QpJGRhdGEKICAgICAgc2F2ZShiYWxhbmNlZF90ZXN0X2RhdGEsIGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX2JhbGFuY2VkX3Rlc3QuUkRhdGEiKQogICAgfSBlbHNlIHsKICAgICAgbG9hZChiYWxhbmNlZF90ZXN0X2RhdGEsIGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX2JhbGFuY2VkX3Rlc3QuUkRhdGEiKQogICAgfQogIH0KICAKICBpZihydW4uc2VsZWN0X1BDKXsKICAgICNzZXBhcmF0ZSB0aGUgZmVhdHVyZXMgZnJvbSBsYWJlbAogICAgZGF0X3RyYWluX25ldyA8LSBiYWxhbmNlZF90cmFpbl9kYXRhWywtZGltKGJhbGFuY2VkX3RyYWluX2RhdGEpWzJdXQogICAgZGF0X3Rlc3RfbmV3IDwtIGJhbGFuY2VkX3Rlc3RfZGF0YVssLWRpbShiYWxhbmNlZF90ZXN0X2RhdGEpWzJdXQogICAgI2NyZWF0ZSBhIHZlY3RvciBjb250YWluIHRhcmdldCBudW1iZXIgb2YgUENzCiAgICBudW0ucGNhIDwtIGMoMTAsNTAsNTAwLDEwMDApCiAgICB0cmFpbl9wY2EgPC0gZnVuY3Rpb24obnVtLnBjYSl7CiAgICAgIGZvcihpIGluIDE6bGVuZ3RoKG51bS5wY2EpKXsKICAgICAgICAjc3RhcnQgdGltZSBmb3IgdHJhaW5pbmcgdGhlIG1vZGVsCiAgICAgICAgdHJhaW4ubW9kZWwuc3RhcnQgPSBwcm9jLnRpbWUoKQogICAgICAgICNydW4gUENBCiAgICAgICAgcGNhIDwtIHByY29tcChkYXRfdHJhaW5fbmV3KQogICAgICAgICNzdG9yZSBmb3IgZWFjaCBwb3RlbnRpYWwgUEMKICAgICAgICB0cmFpbl9wY2EgPC0gZGF0YS5mcmFtZShwY2EkeFssMTpudW0ucGNhW2ldXSwgbGFiZWwgPSBiYWxhbmNlZF90cmFpbl9kYXRhW2RpbShiYWxhbmNlZF90cmFpbl9kYXRhKVsyXV0pCiAgICAgICAgcHJlZF9wY2EgPC0gcHJlZGljdChwY2EsZGF0X3Rlc3RfbmV3KQogICAgICAgIHRlc3RfcGNhIDwtIGRhdGEuZnJhbWUocHJlZF9wY2FbLDE6bnVtLnBjYVtpXV0sIGxhYmVsID0gYmFsYW5jZWRfdGVzdF9kYXRhW2RpbShiYWxhbmNlZF90ZXN0X2RhdGEpWzJdXSkKICAgICAgICAjZml0dGluZyB0aGUgbGRhIG1vZGVsCiAgICAgICAgbGRhX3BjYSA8LSBsZGEobGFiZWwgfiAuLCBkYXRhID0gdHJhaW5fcGNhKSAKICAgICAgICAjc3RvcCB0aW1lIGZvciB0cmFpbmluZyB0aGUgbW9kZWwKICAgICAgICB0cmFpbi5tb2RlbC5lbmQgPSBwcm9jLnRpbWUoKQogICAgICAgICNzdGFydCB0aW1lIGZvciB0ZXN0aW5nIHRoZSBtb2RlbAogICAgICAgIHRlc3QubW9kZWwuc3RhcnQgPSBwcm9jLnRpbWUoKQogICAgICAgICNwcmVkaWN0IGxkYSBtb2RlbAogICAgICAgIGxkYV9wcmVkX3BjYSA9IHByZWRpY3QobGRhX3BjYSx0ZXN0X3BjYVstZGltKHRlc3RfcGNhKVsyXV0pCiAgICAgICAgI2VuZCB0aW1lIGZvciB0ZXN0aW5nIHRoZSBtb2RlbAogICAgICAgIHRlc3QubW9kZWwuZW5kID0gcHJvYy50aW1lKCkKICAgICAgICAjdGVzdCBhY2N1cmFjeQogICAgICAgIHRlc3RfYWNjdXJhY3k9Y29uZnVzaW9uTWF0cml4KGxkYV9wcmVkX3BjYSRjbGFzcywgdGVzdF9wY2EkbGFiZWwpJG92ZXJhbGxbMV0KICAgICAgICBwcmludChsaXN0KGwxPXRyYWluLm1vZGVsLmVuZCAtIHRyYWluLm1vZGVsLnN0YXJ0LAogICAgICAgICAgICAgICBsMj10ZXN0Lm1vZGVsLmVuZCAtIHRlc3QubW9kZWwuc3RhcnQsCiAgICAgICAgICAgICAgIGwzPXRlc3RfYWNjdXJhY3kpKQogICAgICB9CiAgICB9CiAgdHJhaW5fcGNhKG51bS5wY2EpCiAgfQp9CmBgYAoKKipCeSBjb21wYXJpbmcgdGhlIHRyYWluaW5nIHRpbWUsIHRlc3QgdGltZSBhbmQgYWNjdXJhY3ksIHdlIHVzZSBtb2RlbCB3aXRoIDUwIFBDcy4qKgoKKiBNb2RlbCBUcmFpbmluZwoKYGBge3IgZmluYWwgbW9kZWx9CmlmKHJ1bi5wY2FfbGRhKXsKICB0cmFpbi5tb2RlbC5zdGFydCA9IHByb2MudGltZSgpCiAgaWYocnVuLmxkYSl7CiAgICBwY2FfMTAgPC0gcHJjb21wKGRhdF90cmFpbl9uZXcpCiAgICB0cmFpbl9wY2FfMTAgPC0gZGF0YS5mcmFtZShwY2FfMTAkeFssMTo1MF0sIGxhYmVsID0gYmFsYW5jZWRfdHJhaW5fZGF0YVtkaW0oYmFsYW5jZWRfdHJhaW5fZGF0YSlbMl1dKQogICAgcHJlZF9wY2FfMTAgPC0gcHJlZGljdChwY2FfMTAsZGF0X3Rlc3RfbmV3KQogICAgdGVzdF9wY2FfMTAgPC0gZGF0YS5mcmFtZShwcmVkX3BjYV8xMFssMTo1MF0sIGxhYmVsID0gYmFsYW5jZWRfdGVzdF9kYXRhW2RpbShiYWxhbmNlZF90ZXN0X2RhdGEpWzJdXSkKICAgIHNhdmUodHJhaW5fcGNhXzEwLCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV9wY2FfdHJhaW4uUkRhdGEiKQogICAgc2F2ZSh0ZXN0X3BjYV8xMCwgZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfcGNhX3Rlc3QuUkRhdGEiKSAgCiAgfSBlbHNlIHsKICAgIGxvYWQodHJhaW5fcGNhXzEwLCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV9wY2FfdHJhaW4uUkRhdGEiKQogICAgbG9hZCh0ZXN0X3BjYV8xMCwgZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfcGNhX3Rlc3QuUkRhdGEiKSAgCiAgfQogICNjYWxjdWxhdGUgdGhlIHRyYWluaW5nIHRpbWUKICBsZGFfcGNhXzEwIDwtIGxkYShsYWJlbCB+IC4sIGRhdGEgPSB0cmFpbl9wY2FfMTAsIGN2ID0gVFJVRSkKICB0cmFpbi5tb2RlbC5lbmQgPSBwcm9jLnRpbWUoKQp9CmBgYAoKKiBDYWxjdWxhdGUgdGhlIFRyYWluaW5nIGFuZCBUZXN0aW5nIEFjY3VyYWN5IG9mIExEQSBNb2RlbAoKYGBge3J9CmlmKHJ1bi5wY2FfbGRhKXsKICB0ZXN0Lm1vZGVsLnN0YXJ0ID0gcHJvYy50aW1lKCkKICBwcmVkX3RyYWluX2xkYSA8LSBwcmVkaWN0KGxkYV9wY2FfMTAsIHRyYWluX3BjYV8xMFstZGltKHRyYWluX3BjYV8xMClbMl1dKQogIGFjY3VfdHJhaW5fbGRhIDwtIG1lYW4ocHJlZF90cmFpbl9sZGEkY2xhc3MgPT0gdHJhaW5fcGNhXzEwJGxhYmVsKQogIGNhdCgiVGhlIHRyYWluaWcgYWNjdXJhY3kgb2YgbW9kZWw6IExEQSIsICJpcyIsIGFjY3VfdHJhaW5fbGRhKjEwMCwgIiUuXG4iKQogICNjYWxjdWxhdGluZyB0aGUgdGVzdCB0aW1lCiAgaWYocnVuLnRlc3QpewogICAgcHJlZF90ZXN0X2xkYSA8LSBwcmVkaWN0KGxkYV9wY2FfMTAsIHRlc3RfcGNhXzEwKQogIH0KICB0ZXN0Lm1vZGVsLmVuZCA9IHByb2MudGltZSgpCiAgc2F2ZShwcmVkX3Rlc3RfbGRhLCBmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluLlJEYXRhIikKICBhY2N1X3Rlc3RfbGRhIDwtIG1lYW4ocHJlZF90ZXN0X2xkYSRjbGFzcyA9PSB0ZXN0X3BjYV8xMCRsYWJlbCkKICBjYXQoIlRoZSBhY2N1cmFjeSBvZiBtb2RlbDogTERBIiwgImlzIiwgYWNjdV90ZXN0X2xkYSoxMDAsICIlLlxuIikKICB0cHIuZnByIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMocHJlZF90ZXN0X2xkYSRjbGFzcyksIHRlc3RfcGNhXzEwJGxhYmVsKQogIGxkYV9hdWMgPSBXZWlnaHRlZEFVQyh0cHIuZnByKQogIGNhdCgiVGhlIEFVQyBvZiBtb2RlbDogTERBIGlzIiwgbGRhX2F1YywgIi5cbiIpCn0KYGBgCgoqIFN1bW1hcml6ZSBSdW5uaW5nIFRpbWUKClByZWRpY3Rpb24gcGVyZm9ybWFuY2UgbWF0dGVycywgc28gZG9lcyB0aGUgcnVubmluZyB0aW1lcyBmb3IgY29uc3RydWN0aW5nIGZlYXR1cmVzIGFuZCBmb3IgdHJhaW5pbmcgdGhlIG1vZGVsLCBlc3BlY2lhbGx5IHdoZW4gdGhlIGNvbXB1dGF0aW9uIHJlc291cmNlIGlzIGxpbWl0ZWQuIAoKYGBge3J9CmlmKHJ1bi5wY2FfbGRhKXsKICB0bV90cmFpbiA8LSB0cmFpbi5tb2RlbC5lbmQgLSB0cmFpbi5tb2RlbC5zdGFydAogIHRtX3Rlc3QgPC0gdGVzdC5tb2RlbC5lbmQgLSB0ZXN0Lm1vZGVsLnN0YXJ0CiAgY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdHJhaW5pbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQogIGNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIHRlc3RpbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90ZXN0WzFdLCAicyBcbiIpCiAgY2F0KCJUaW1lIGZvciB0cmFpbmluZyBtb2RlbD0iLCB0bV90cmFpblsxXSwgInMgXG4iKSAKICBjYXQoIlRpbWUgZm9yIHRlc3RpbmcgbW9kZWw9IiwgdG1fdGVzdFsxXSwgInMgXG4iKQp9CmBgYAoKKiBHZW5lcmF0ZSBhIGNzdiBvbiBwcmVzZW50YXRpb24gZGF5CgpgYGB7ciBnZW5lcmF0ZSBjc3Z9CmlmIChydW4ucHJlc2VudGF0aW9uLmRheSl7CiAgY3N2ZmlsZW91dHB1dDwtIi4uL291dHB1dC9sYWJlbF9wcmVkaWN0aW9uLmNzdiIKICAjQWR2YW5jZWQ8LQogIEJhc2VsaW5lPC1sYWJlbF9wcmVkX2Jhc2VsaW5lCiAgSW5kZXg8LXRlc3RfaWR4CiAgY3N2ZGF0YSA8LSBkYXRhLmZyYW1lKEluZGV4LCBCYXNlbGluZSkKICAKICB3cml0ZS5jc3YoY3N2ZGF0YSxjc3ZmaWxlb3V0cHV0LCByb3cubmFtZXM9RkFMU0UscXVvdGUgPSBGQUxTRSkKICAKICAKICAjcHJlc2VudGF0aW9uX2RheV9nYm1fYWNjdSA8LSBtZWFuKGluZm8kbGFiZWwgPT0gbGFiZWxfcHJlZF9iYXNlbGluZSkKICAjY2F0KCJUaGUgYWNjdXJhY3kgb2YgR0JNIGJhc2VsaW5lIG1vZGVsIGlzIiwgcHJlc2VudGF0aW9uX2RheV9nYm1fYWNjdSoxMDAsICIlLlxuIikKICAKfQpgYGAKCiMjIyBSZWZlcmVuY2UKLSBEdSwgUy4sIFRhbywgWS4sICYgTWFydGluZXosIEEuIE0uICgyMDE0KS4gQ29tcG91bmQgZmFjaWFsIGV4cHJlc3Npb25zIG9mIGVtb3Rpb24uIFByb2NlZWRpbmdzIG9mIHRoZSBOYXRpb25hbCBBY2FkZW15IG9mIFNjaWVuY2VzLCAxMTEoMTUpLCBFMTQ1NC1FMTQ2Mi4=